From 6e31d44685732b6327d737ad73569f355779a3f7 Mon Sep 17 00:00:00 2001 From: simonGraband Date: Mon, 16 Dec 2019 13:37:31 +0100 Subject: [PATCH] =?UTF-8?q?Introduce=20E2E=20Testing=20using=20TestC=C3=A1?= =?UTF-8?q?fe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `yarn e2etest` in the client folder starts theia and the tests via a package.json script. Signed-off-by: simonGraband --- client/.testcaferc.json | 3 + client/package.json | 6 +- client/tests/config.json | 3 + client/tests/test.ts | 575 +++++++++++++++++++++++++++++++++++++ client/tests/tsconfig.json | 8 + client/yarn.lock | 120 +++++++- 6 files changed, 711 insertions(+), 4 deletions(-) create mode 100644 client/.testcaferc.json create mode 100644 client/tests/config.json create mode 100644 client/tests/test.ts create mode 100644 client/tests/tsconfig.json diff --git a/client/.testcaferc.json b/client/.testcaferc.json new file mode 100644 index 0000000..3ed31fe --- /dev/null +++ b/client/.testcaferc.json @@ -0,0 +1,3 @@ +{ + "tsConfigPath": "./tests/tsconfig.json" +} diff --git a/client/package.json b/client/package.json index 0edb68f..fe57617 100644 --- a/client/package.json +++ b/client/package.json @@ -8,10 +8,14 @@ "watch": "lerna run --parallel watch", "publish": "yarn && yarn publish:latest", "publish:latest": "lerna publish", - "publish:next": "lerna publish --exact --canary=next --npm-tag=next --yes" + "publish:next": "lerna publish --exact --canary=next --npm-tag=next --yes", + "theia:start": "cd browser-app && yarn start", + "testcafe:start": "testcafe chrome tests/test.ts", + "e2etest": "npm-run-all --parallel --aggregate-output theia:start testcafe:start" }, "devDependencies": { "lerna": "2.4.0", + "npm-run-all": "^4.1.5", "tslint": "^5.5.0" }, "resolutions": { diff --git a/client/tests/config.json b/client/tests/config.json new file mode 100644 index 0000000..737815e --- /dev/null +++ b/client/tests/config.json @@ -0,0 +1,3 @@ +{ + "defaultPort": 3000 +} diff --git a/client/tests/test.ts b/client/tests/test.ts new file mode 100644 index 0000000..12b0ce0 --- /dev/null +++ b/client/tests/test.ts @@ -0,0 +1,575 @@ +/******************************************************************************* + * Copyright (c) 2019 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 { join, resolve } from "path"; +import { Selector } from "testcafe"; + +import * as config from "./config.json"; + +const relPathToWorkspace = resolve(join(__dirname, '..', 'workspace')); + +class Helper { + static load = async t => { + await t + .wait(5000); + await Selector('.p-MenuBar-content', { visibilityCheck: true }); + } + + static emptyEcore = () => { + return Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('empty.ecore'); + } + + static glspGraphEcore = () => { + return Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('glsp-graph.ecore'); + } + + static wsSelect = () => { + return Selector('#shell-tab-explorer-view-container'); + } +} + +class CreateHelper { + + constructor(private t: TestController) { + } + async createNode(name) { + const selector = Selector('div.tool-group > div.tool-button').withText(name); + const svgCanvas = Selector('svg.sprotty-graph'); + await this.t.click(selector) + .click(svgCanvas); + } + + async createClass() { + return this.createNode('Class'); + } + + async createAbstract() { + return this.createNode('Abstract'); + } + + async createInterface() { + return this.createNode('Interface'); + } + + async createEnum() { + return this.createNode('Enum'); + } + + async createAllNodeTypes() { + const emptyEcore = Helper.emptyEcore(); + const wsSelect = Helper.wsSelect(); + + await this.t + .click(wsSelect) + .click(emptyEcore); + await this.createClass(); + await this.createAbstract(); + await this.createEnum(); + await this.createInterface(); + } + + async createEdge(name, a, b) { + const selector = Selector('div.tool-group > div.tool-button').withText(name); + await this.t.click(selector).click(a).hover(b).click(b); + } + + async layout() { + await this.t.pressKey('alt+l').wait(500); + } + async createAllEdges() { + await this.layout(); + await this.createEdge('Reference', this.nodesSelector.classNode, this.nodesSelector.abstractNode); + await this.createEdge('Composition', this.nodesSelector.interfaceNode, this.nodesSelector.classNode); + await this.createEdge('Inheritance', this.nodesSelector.interfaceNode, this.nodesSelector.abstractNode); + } + + async deleteAllNodes(t) { + await t + .click(this.nodesSelector.classNode) + .pressKey('delete') + .click(this.nodesSelector.abstractNode) + .pressKey('delete') + .click(this.nodesSelector.enumNode) + .pressKey('delete') + .click(this.nodesSelector.interfaceNode) + .pressKey('delete'); + } + + async addAttributes() { + const attribute = Selector('div.tool-group > div.tool-button').withText("Attribute"); + const literal = Selector('div.tool-group > div.tool-button').withText("Literal"); + + this.layout(); + + await this.t + .click(attribute) + .click(this.nodesSelector.classNode) + .click(attribute) + .click(this.nodesSelector.abstractNode) + .click(attribute) + .click(this.nodesSelector.interfaceNode) + .click(literal) + .click(this.nodesSelector.enumNode); + } + + public nodesSelector = { + classNode: Selector('g.node.ecore-node text.name.sprotty-label').withText('NewEClass0'), + abstractNode: Selector('g.node.ecore-node.abstract text.name.sprotty-label').withText('NewEClass1'), + enumNode: Selector('g.node.ecore-node text.name.sprotty-label').withText('NewEEnum2'), + interfaceNode: Selector('g.node.ecore-node.interface text.name.sprotty-label').withText('NewEClass3') + }; + + public edgeSelector = { + inheritanceEdge: Selector('g.sprotty-edge.ecore-edge.inheritance'), + interfaceEdge: Selector('g.sprotty-edge.ecore-edge.composition text.edge.sprotty-label').withText('[0..1] neweclass0s'), + referenceEdge: Selector('g.sprotty-edge.ecore-edge text.edge.sprotty-label').withText('[0..1] neweclass1s') + }; + + async getEdgePosition(s: Selector) { + const x = await (s.getAttribute('cx')); + const y = await (s.getAttribute('cy')); + return { x: parseFloat(x), y: parseFloat(y) }; + } + + async getNodePosition(s: Selector) { + const transform = await (s.parent("g.node.ecore-node").getAttribute('transform')); + const regex = new RegExp("\\s*\\w*\\s*\\(\\s*(\\d+(\\.\\d+)?)\\s*,\\s*(\\d+(\\.\\d+)?)\\s*\\)\\s*"); + const match = regex.exec(transform); + if (match) { + return { x: parseFloat(match[1]), y: parseFloat(match[3]) }; + } + return {}; + } +} + +const checkDefaultWorkbench = async (t) => { + const emptyEcore = Helper.emptyEcore(); + const glspGraphEcore = Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('glsp-graph.ecore'); + const glspGraphEnotation = Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('glsp-graph.enotation'); + const umlEcore = Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('UML.ecore'); + await t + .expect(emptyEcore.exists).ok('Check if empty.ecore exists') + .expect(glspGraphEcore.exists).ok('Check if glsp-graph.ecore exists') + .expect(glspGraphEnotation.exists).ok('Check if glsp-graph.enotation exists') + .expect(umlEcore.exists).ok('Check if UML.ecore exists') + .expect(Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').count).eql(4); +}; + +const openQuickAccessBar = async (t) => { + const viewMenu = Selector('.p-MenuBar-item').withText('View'); + const findCommand = Selector('.p-Menu-itemLabel').withText('Find Command...'); + + await t + .click(viewMenu) + .click(findCommand); +}; + +const writeQuickAccessBar = async (t, text) => { + const quickAccess = Selector('div.monaco-inputbox.idle input.input'); + + await t + .typeText(quickAccess, text) + .pressKey('Enter'); +}; + +const port = process.env.PORT || config.defaultPort; + +fixture`Ecore-glsp E2E-Testing`// declare the fixture + .page`http://localhost:${port}/#${relPathToWorkspace}` + .beforeEach(async t => { + await Helper.load(t); + }); // The start page loads the workbench at the above defined Path + +test('Open Workbench', async t => { + const workspace = Helper.wsSelect(); + await t + .click(workspace); + await checkDefaultWorkbench(t); +}); + +test('Switch Theme', async t => { + + openQuickAccessBar(t); + writeQuickAccessBar(t, "Change Color Theme"); + writeQuickAccessBar(t, "Dark"); + + await t + .expect(Selector('div.p-Widget.p-DockPanel.p-SplitPanel-child').getStyleProperty('color')).eql('rgb(224, 224, 224)'); + + openQuickAccessBar(t); + writeQuickAccessBar(t, "Change Color Theme"); + writeQuickAccessBar(t, "Light"); + + await t + .expect(Selector('div.p-Widget.p-DockPanel.p-SplitPanel-child').getStyleProperty('color')).eql('rgb(97, 97, 97)'); +}); + +test('Open graph-glsp.ecore', async t => { + const wsSelect = Helper.wsSelect(); + const fileSelect = Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('glsp-graph.ecore'); + + await t + .click(wsSelect) + .click(fileSelect) + .expect(Selector('text.name.sprotty-label').withText('GSeverity').exists).ok('Class GSeverity exists') + .expect(Selector('text.name.sprotty-label').count).eql(50); +}); + +test('Deletion/Renaming of enotation', async t => { + const wsSelect = Helper.wsSelect(); + const fileSelect = Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('glsp-graph.enotation'); + const duplicateFile = Selector('.p-Menu-itemLabel').withText('Duplicate'); + const deleteFile = Selector('.p-Menu-itemLabel').withText('Delete'); + const renameFile = Selector('.p-Menu-itemLabel').withText('Rename'); + const okButton = Selector('.theia-button.main').withText('OK'); + const duplicateFileSelect = Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('glsp-graph_copy.enotation'); + const renameInput = Selector('div.dialogContent > input'); + + await t + .click(wsSelect) + .rightClick(fileSelect) + .click(duplicateFile) + .wait(200) + .rightClick(fileSelect) + .click(deleteFile) + .click(okButton) + .expect(Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').count).eql(4) + + .rightClick(duplicateFileSelect) + .click(renameFile) + .pressKey('ctrl+a') + .typeText(renameInput, "glsp-graph.enotation") + .pressKey('Enter'); + +}).after(checkDefaultWorkbench); + +test('Open Ecore without enotation', async t => { + const wsSelect = Helper.wsSelect(); + const fileSelect = Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('UML.ecore'); + const classSelect = Selector("text.name.sprotty-label").withText("ConnectorKind").parent("g.node.ecore-node"); + + await t + .click(wsSelect) + .click(fileSelect).wait(10000) // Necessary to wait as the layouting needs to be executed. + .expect(classSelect.getAttribute('transform')).notEql('translate(0, 0)'); +}); + +test('Create and Delete ecore file', async t => { + const wsSelect = Helper.wsSelect(); + const deleteFile = Selector('.p-Menu-itemLabel').withText('Delete'); + const okButton = Selector('.theia-button.main').withText('OK'); + const newEcore = Selector('div.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('Test.ecore'); + + openQuickAccessBar(t); + writeQuickAccessBar(t, "New Ecore-File"); + writeQuickAccessBar(t, "Test"); + writeQuickAccessBar(t, "testPrefix"); + writeQuickAccessBar(t, "testURI"); + + await t + .click(wsSelect) + .wait(500) + .expect(newEcore.exists).ok("File was not properply generated") + .rightClick(newEcore) + .click(deleteFile) + .click(okButton) + .expect(newEcore.exists).notOk("File was still found, even though it should be deleted"); + +}).after(checkDefaultWorkbench); + +test('Create Nodes', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.layout(); + + await t + .expect(creator.nodesSelector.classNode.exists).ok("Class has been created") + .expect(creator.nodesSelector.abstractNode.exists).ok("Abstract has been created") + .expect(creator.nodesSelector.enumNode.exists).ok("Enum has been created") + .expect(creator.nodesSelector.interfaceNode.exists).ok("Interface has been created") + .pressKey('ctrl+s'); + + // Check Serialization + const fileSelect = Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('empty.enotation'); + const emptyFile = Selector('.theia-TreeNodeSegment.theia-TreeNodeSegmentGrow').withText('empty.ecore'); + const openWith = Selector('.p-Menu-itemLabel').withText('Open With'); + const codeEditor = Selector('.p-Menu-itemLabel').withText('Code Editor'); + const line = Selector('.view-lines'); + + // Enotation + await t + .click(fileSelect) + .expect(line.child().child().withText('NewEClass0').exists).ok('Enotation serialization worked'); + + // Ecore + await t + .rightClick(emptyFile) + .hover(openWith) + .click(codeEditor) + .click(emptyFile) + .expect(line.child().child().withText('NewEClass0').exists).ok('Ecore serialization worked'); + + + // Delete again + const deleteFile = Selector('.p-Menu-itemLabel').withText('Delete'); + const okButton = Selector('.theia-button.main').withText('OK'); + creator.deleteAllNodes(t); + await t + .pressKey('ctrl+s') + .rightClick(fileSelect) + .click(deleteFile) + .click(okButton); +}).after(checkDefaultWorkbench); + +// Could check for Serialization +test('Move Class', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.layout(); + const drag_distance = 100; + const original_pos = await creator.getNodePosition(creator.nodesSelector.classNode); + await t + .drag(creator.nodesSelector.classNode, drag_distance, drag_distance, { speed: 0.1 }); + const new_pos = await creator.getNodePosition(creator.nodesSelector.classNode); + await t + .expect(original_pos.x + drag_distance).eql(new_pos.x) + .expect(original_pos.y + drag_distance).eql(new_pos.y); +}); + +test('Delete Nodes with Eraser', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + const eraser = Selector('.fas.fa-eraser.fa-xs'); + await creator.layout(); + await t + .click(eraser) + .click(creator.nodesSelector.classNode) + .click(eraser) + .click(creator.nodesSelector.abstractNode) + .click(eraser) + .click(creator.nodesSelector.enumNode) + .click(eraser) + .click(creator.nodesSelector.interfaceNode) + .expect(creator.nodesSelector.classNode.exists).notOk("Class has been deleted") + .expect(creator.nodesSelector.abstractNode.exists).notOk("Abstract has been deleted") + .expect(creator.nodesSelector.enumNode.exists).notOk("Enum has been deleted") + .expect(creator.nodesSelector.interfaceNode.exists).notOk("Interface has been deleted"); +}); + +test('Delete Nodes with ENTF', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.layout(); + await creator.deleteAllNodes(t); + await t + .expect(creator.nodesSelector.classNode.exists).notOk("Class has been deleted") + .expect(creator.nodesSelector.abstractNode.exists).notOk("Abstract has been deleted") + .expect(creator.nodesSelector.enumNode.exists).notOk("Enum has been deleted") + .expect(creator.nodesSelector.interfaceNode.exists).notOk("Interface has been deleted"); +}); + +test('Create Edges', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + + await creator.createAllEdges(); + + await t.expect(creator.edgeSelector.referenceEdge.exists).ok('Reference exists'); + await t.expect(creator.edgeSelector.interfaceEdge.exists).ok('Composition exists'); + await t.expect(creator.edgeSelector.inheritanceEdge.exists).ok('Inheritance exists'); +}); + +test('Move Edges', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.createAllEdges(); + await creator.layout(); + + const points = Selector('.sprotty-edge.ecore-edge.selected').child().withAttribute('data-kind', 'manhattan-50%'); + const svgCanvas = Selector('svg.sprotty-graph'); + + await t + .click(creator.edgeSelector.referenceEdge); + + const original_pos1 = await creator.getEdgePosition(points.nth(0)); + const original_pos2 = await creator.getEdgePosition(points.nth(1)); + const original_pos3 = await creator.getEdgePosition(points.nth(2)); + + await t + .drag(points.nth(0), -10, 0, { speed: 0.1 }) + .click(svgCanvas) + .click(creator.edgeSelector.referenceEdge) + .drag(points.nth(1), 0, -10, { speed: 0.1 }) + .click(svgCanvas) + .click(creator.edgeSelector.referenceEdge) + .drag(points.nth(2), 10, 0, { speed: 0.1 }) + .click(svgCanvas) + .click(creator.edgeSelector.referenceEdge); + + const new_pos1 = await creator.getEdgePosition(points.nth(0)); + const new_pos2 = await creator.getEdgePosition(points.nth(1)); + const new_pos3 = await creator.getEdgePosition(points.nth(2)); + + await t + .expect(original_pos1.x - 10).eql(new_pos1.x) + .expect(original_pos1.y).eql(new_pos1.y) + .expect(original_pos2.y - 10).eql(new_pos2.y) + .expect(original_pos3.x + 10).eql(new_pos3.x); +}); +test('Delete Edges', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.createAllEdges(); + const eraser = Selector('.fas.fa-eraser.fa-xs'); + await creator.layout(); + await t + .click(eraser) + .click(creator.edgeSelector.referenceEdge) + .click(eraser) + .click(creator.edgeSelector.interfaceEdge) + .click(eraser) + .click(creator.edgeSelector.inheritanceEdge); + await t.expect(creator.edgeSelector.referenceEdge.exists).notOk('Reference deleted'); + await t.expect(creator.edgeSelector.interfaceEdge.exists).notOk('Composition deleted'); + await t.expect(creator.edgeSelector.inheritanceEdge.exists).notOk('Inheritance deleted'); +}); + +test('Delete Edges with ENTF', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.createAllEdges(); + await creator.layout(); + await t + .click(creator.edgeSelector.referenceEdge) + .pressKey('delete') + .click(creator.edgeSelector.interfaceEdge) + .pressKey('delete') + .click(creator.edgeSelector.inheritanceEdge) + .pressKey('delete'); + await t.expect(creator.edgeSelector.referenceEdge.exists).notOk('Reference deleted'); + await t.expect(creator.edgeSelector.interfaceEdge.exists).notOk('Composition deleted'); + await t.expect(creator.edgeSelector.inheritanceEdge.exists).notOk('Inheritance deleted'); +}); + +test('Add Attributes/Literals', async t => { + const creator = new CreateHelper(t); + const attributeClass = Selector('g.node.ecore-node text.sprotty-label').withText('NewEAttribute8 : EString'); + const attributeAbstract = Selector('g.node.ecore-node text.sprotty-label').withText('NewEAttribute9 : EString'); + const attributeInterface = Selector('g.node.ecore-node text.sprotty-label').withText('NewEAttribute10 : EString'); + const attributeEnum = Selector('g.node.ecore-node text.sprotty-label').withText('NewEEnumLiteral11'); + + await creator.createAllNodeTypes(); + await creator.addAttributes(); + + await t + .expect(attributeClass.exists).ok("Adding Attribute to Class") + .expect(attributeAbstract.exists).ok("Adding Attribute to Abstract") + .expect(attributeInterface.exists).ok("Adding Attribute to Interface") + .expect(attributeEnum.exists).ok("Adding Literal to Enum"); +}); + +// Could check for Serialization +test('Renaming Classes/Attributes', async t => { + const creator = new CreateHelper(t); + const nameClass = creator.nodesSelector.classNode; + const nameAbstract = creator.nodesSelector.abstractNode; + const nameInterface = creator.nodesSelector.interfaceNode; + const nameEnum = creator.nodesSelector.enumNode; + const attributeClass = Selector('g.node.ecore-node text.sprotty-label').withText('NewEAttribute8 : EString'); + const attributeAbstract = Selector('g.node.ecore-node text.sprotty-label').withText('NewEAttribute9 : EString'); + const attributeInterface = Selector('g.node.ecore-node text.sprotty-label').withText('NewEAttribute10 : EString'); + const attributeEnum = Selector('g.node.ecore-node text.sprotty-label').withText('NewEEnumLiteral11'); + const attributeClassRenamed = Selector('g.node.ecore-node text.sprotty-label').withText('TestAttributeClass : EString'); + const attributeAbstractRenamed = Selector('g.node.ecore-node text.sprotty-label').withText('testAttributeAbstract : EString'); + const attributeInterfaceRenamed = Selector('g.node.ecore-node text.sprotty-label').withText('TestAttributeInterface : EString'); + const attributeEnumRenamed = Selector('g.node.ecore-node text.sprotty-label').withText('TestLiteralEnum'); + const nameClassRenamed = Selector('g.node.ecore-node text.name.sprotty-label').withText('NewEClass0'); + const nameAbstractRenamed = Selector('g.node.ecore-node text.name.sprotty-label').withText('NewEClass1'); + const nameInterfaceRenamed = Selector('g.node.ecore-node text.name.sprotty-label').withText('NewEClass2'); + const nameEnumRenamed = Selector('g.node.ecore-node text.name.sprotty-label').withText('NewEEnum3'); + const input = Selector('div.label-edit input'); + + await creator.createAllNodeTypes(); + await creator.addAttributes(); + + await t + .doubleClick(nameClass) + .typeText(input, "TestClass") + .click(nameAbstract) + .doubleClick(nameAbstract) + .typeText(input, "TestAbstract") + .click(nameInterface) + .doubleClick(nameInterface) + .typeText(input, "TestInterface") + .click(nameEnum) + .doubleClick(nameEnum) + .typeText(input, "TestEnum") + .click(attributeClass) + .doubleClick(attributeClass) + .typeText(input, "TestAttributeClass") + .click(attributeAbstract) + .doubleClick(attributeAbstract) + .typeText(input, "TestAttributeAbstract") + .click(attributeInterface) + .doubleClick(attributeInterface) + .typeText(input, "TestAttributeInterface") + .click(attributeEnum) + .doubleClick(attributeEnum) + .typeText(input, "TestLiteralEnum") + .expect(nameClassRenamed.exists).ok("Renamed Class") + .expect(nameAbstractRenamed.exists).ok("Renamed Abstract") + .expect(nameInterfaceRenamed.exists).ok("Renamed Interface") + .expect(nameEnumRenamed.exists).ok("Renamed Enum") + .expect(attributeClassRenamed.exists).ok("Renamed Attribute in Class") + .expect(attributeAbstractRenamed.exists).ok("Renamed Attribute in Abstract") + .expect(attributeInterfaceRenamed.exists).ok("Renamed Attribute in Interface") + .expect(attributeEnumRenamed.exists).ok("Renamed Literal in Enum"); +}); + +// Could check for Serialization +test('Change Attributetype', async t => { + const wsSelect = Helper.wsSelect(); + const glspEcore = Helper.glspGraphEcore(); + const attributeSelector = Selector('g.node.ecore-node text.sprotty-label').withText('layout : EString'); + const changedAttribute = Selector('g.node.ecore-node text.sprotty-label').withText('test : EDate'); + const changedAttributeWrite = Selector('g.node.ecore-node text.sprotty-label').withText('layout : EString'); + const input = Selector('div.label-edit input'); + + await t + .click(wsSelect) + .click(glspEcore) + .doubleClick(attributeSelector) + .typeText(input, 'test : EDa') + .pressKey('ctrl+space') + .pressKey('down') + .pressKey('Enter') + .expect(changedAttribute.exists).ok("Changing the attributetype via Autocompletion") + .doubleClick(changedAttribute) + .typeText(input, 'layout : EString') + .pressKey('Enter') + .expect(changedAttributeWrite.exists).ok("Changing the attributetype via Typing"); +}); + +test('Style new Diagram', async t => { + const creator = new CreateHelper(t); + await creator.createAllNodeTypes(); + await creator.createAllEdges(); + + await t.pressKey('alt+l').wait(500) + .expect(creator.nodesSelector.enumNode.parent("g.node.ecore-node").getAttribute('transform')).eql('translate(12, 12)') + .expect(creator.nodesSelector.classNode.parent("g.node.ecore-node").getAttribute('transform')).notEql('translate(12, 12)') + .expect(creator.nodesSelector.interfaceNode.parent("g.node.ecore-node").getAttribute('transform')).notEql('translate(12, 12)') + .expect(creator.nodesSelector.abstractNode.parent("g.node.ecore-node").getAttribute('transform')).notEql('translate(12, 12)'); +}); diff --git a/client/tests/tsconfig.json b/client/tests/tsconfig.json new file mode 100644 index 0000000..27b3e1e --- /dev/null +++ b/client/tests/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true + } +} diff --git a/client/yarn.lock b/client/yarn.lock index 462363a..398e3d4 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -4060,7 +4060,7 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -define-properties@^1.1.2: +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -4369,6 +4369,32 @@ error@^7.0.2: dependencies: string-template "~0.2.1" +es-abstract@^1.17.0-next.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0.tgz#f42a517d0036a5591dbb2c463591dc8bb50309b1" + integrity sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es6-promise@^4.0.3, es6-promise@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -5357,6 +5383,11 @@ has-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" @@ -5400,7 +5431,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1: +has@^1.0.1, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -5778,6 +5809,11 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + is-ci@^1.0.10: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" @@ -5799,6 +5835,11 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -5959,6 +6000,13 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" @@ -5988,6 +6036,13 @@ is-svg@^2.0.0: dependencies: html-comment-regex "^1.1.0" +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + is-text-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" @@ -6791,6 +6846,11 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -7348,6 +7408,21 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + npm-run-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f" @@ -7411,7 +7486,12 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.11, object-keys@^1.0.12: +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -7842,6 +7922,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= +pidtree@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" + integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg== + pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -9165,6 +9250,11 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + shelljs@^0.8.0: version "0.8.3" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" @@ -9537,6 +9627,30 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string.prototype.padend@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" + integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string_decoder@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"