diff --git a/client/tests/e2e/runTests.ts b/client/tests/e2e/runTests.ts index e810ed5..c871f34 100644 --- a/client/tests/e2e/runTests.ts +++ b/client/tests/e2e/runTests.ts @@ -2,11 +2,11 @@ import * as path from "path"; import { runTests } from "@vscode/test-electron"; -async function main() { +async function main(): Promise { try { // The folder containing the Extension Manifest package.json // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, "../../"); + const extensionDevelopmentPath = path.resolve(__dirname, "../../../"); // The path to test runner // Passed to --extensionTestsPath diff --git a/client/tests/e2e/suite/extension.e2e.ts b/client/tests/e2e/suite/extension.e2e.ts index d958ab4..817636c 100644 --- a/client/tests/e2e/suite/extension.e2e.ts +++ b/client/tests/e2e/suite/extension.e2e.ts @@ -1,14 +1,27 @@ -import * as assert from "assert"; - // You can import and use all API from the 'vscode' module // as well as import your extension to test it import * as vscode from "vscode"; -// import * as myExtension from '../../extension'; +import * as path from "path"; +import * as assert from "assert"; +import { activateAndOpenInEditor, getDocUri, closeAllEditors, openDocument, sleep } from "./helpers"; suite("Extension Test Suite", () => { - vscode.window.showInformationMessage("Start all tests."); - - test("Sample test", () => { - assert.strictEqual([1, 2, 3].indexOf(5), -1); + teardown(closeAllEditors); + suite("Native (JSON) Workflows", () => { + suite("Commands Tests", () => { + test("Clean workflow command removes non-essential properties", async () => { + const dirtyDocUri = getDocUri(path.join("json", "clean", "wf_01_dirty.ga")); + const cleanDocUri = getDocUri(path.join("json", "clean", "wf_01_clean.ga")); + const { document } = await activateAndOpenInEditor(dirtyDocUri); + const dirtyDoc = document.getText(); + await vscode.commands.executeCommand("galaxy-workflows.cleanWorkflow"); + await sleep(1000); // Wait for command to apply changes + const actualCleanJson = document.getText(); + assert.notEqual(dirtyDoc, actualCleanJson); + const expectedCleanDocument = await openDocument(cleanDocUri); + const expectedCleanJson = expectedCleanDocument.getText(); + assert.strictEqual(actualCleanJson, expectedCleanJson); + }); + }); }); }); diff --git a/client/tests/e2e/suite/helpers.ts b/client/tests/e2e/suite/helpers.ts new file mode 100644 index 0000000..a4b459d --- /dev/null +++ b/client/tests/e2e/suite/helpers.ts @@ -0,0 +1,84 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import * as assert from "assert"; + +/** + * Contains the document and its corresponding editor + */ +export interface DocumentEditor { + editor: vscode.TextEditor; + document: vscode.TextDocument; +} + +export async function activate(): Promise { + const ext = vscode.extensions.getExtension("davelopez.galaxy-workflows"); + const api = ext.isActive ? ext.exports : await ext.activate(); + return api; +} + +export async function openDocumentInEditor(docUri: vscode.Uri): Promise { + try { + const document = await vscode.workspace.openTextDocument(docUri); + const editor = await vscode.window.showTextDocument(document); + return { + editor, + document, + }; + } catch (e) { + console.error(e); + } +} + +export async function openDocument(docUri: vscode.Uri): Promise { + try { + const document = await vscode.workspace.openTextDocument(docUri); + return document; + } catch (e) { + console.error(e); + } +} + +export async function activateAndOpenInEditor(docUri: vscode.Uri): Promise { + await activate(); + const documentEditor = await openDocumentInEditor(docUri); + await sleep(2000); // Wait for server activation + return documentEditor; +} + +export async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export const getDocPath = (filePath: string): string => { + return path.resolve(__dirname, path.join("..", "..", "..", "..", "test-data", filePath)); +}; + +export const getDocUri = (filePath: string): vscode.Uri => { + return vscode.Uri.file(getDocPath(filePath)); +}; + +export async function assertDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]): Promise { + const actualDiagnostics = vscode.languages.getDiagnostics(docUri); + + assert.equal(actualDiagnostics.length, expectedDiagnostics.length); + + expectedDiagnostics.forEach((expectedDiagnostic, i) => { + const actualDiagnostic = actualDiagnostics[i]; + assert.equal(actualDiagnostic.message, expectedDiagnostic.message); + assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range); + assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity); + }); +} + +/** + * Asserts that the given workflow document has no diagnostics i.e. is valid. + * @param docUri Workflow document URI + */ +export async function assertValid(docUri: vscode.Uri): Promise { + const actualDiagnostics = vscode.languages.getDiagnostics(docUri); + assert.equal(actualDiagnostics.length, 0); +} + +export function closeAllEditors(): Thenable { + return vscode.commands.executeCommand("workbench.action.closeAllEditors"); +} diff --git a/client/tests/e2e/suite/index.ts b/client/tests/e2e/suite/index.ts index e0dc946..e6f82f1 100644 --- a/client/tests/e2e/suite/index.ts +++ b/client/tests/e2e/suite/index.ts @@ -7,6 +7,8 @@ export function run(): Promise { const mocha = new Mocha({ ui: "tdd", color: true, + timeout: 60000, + inlineDiffs: true, }); const testsRoot = path.resolve(__dirname, ".."); diff --git a/package.json b/package.json index da7a1b5..53ab707 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "test-unit-client": "cd client && npm run test-unit && cd ..", "test-unit-server": "cd server && npm run test-unit && cd ..", "test-compile": "tsc --project ./client --outDir client/out", - "pretest:e2e": "npm run test-compile", + "pretest:e2e": "npm run compile && npm run test-compile", "test:e2e": "node ./client/out/e2e/runTests.js" }, "devDependencies": { diff --git a/server/src/commands/cleanWorkflow.ts b/server/src/commands/cleanWorkflow.ts index 1432591..15307ea 100644 --- a/server/src/commands/cleanWorkflow.ts +++ b/server/src/commands/cleanWorkflow.ts @@ -105,7 +105,10 @@ export class CleanWorkflowCommand extends CustomCommand { // Remove trailing comma in previous property node const isLastNode = workflowDocument.isLastNodeInParent(node); if (isLastNode) { - const previousNode = workflowDocument.getPreviousSiblingNode(node); + let previousNode = workflowDocument.getPreviousSiblingNode(node); + while (previousNode && nodesToRemove.includes(previousNode)) { + previousNode = workflowDocument.getPreviousSiblingNode(previousNode); + } if (previousNode) { const range = this.getFullNodeRange(workflowDocument.textDocument, previousNode); const nodeText = workflowDocument.textDocument.getText(range); @@ -132,10 +135,7 @@ export class CleanWorkflowCommand extends CustomCommand { return result; } - private getNonEssentialNodes( - workflowDocument: WorkflowDocument, - cleanablePropertyNames: Set - ): PropertyASTNode[] { + private getNonEssentialNodes(workflowDocument: WorkflowDocument, cleanablePropertyNames: Set): ASTNode[] { const root = workflowDocument.rootNode; if (!root) { return []; diff --git a/test-data/clean-diff/workflow_with_revisions.ga b/test-data/json/clean-diff/workflow_with_revisions.ga similarity index 100% rename from test-data/clean-diff/workflow_with_revisions.ga rename to test-data/json/clean-diff/workflow_with_revisions.ga diff --git a/test-data/json/clean/wf_01_clean.ga b/test-data/json/clean/wf_01_clean.ga new file mode 100644 index 0000000..de9b3a0 --- /dev/null +++ b/test-data/json/clean/wf_01_clean.ga @@ -0,0 +1,71 @@ +{ + "a_galaxy_workflow": "true", + "annotation": "This is a cool workflow", + "format-version": "0.1", + "license": "MIT", + "name": "Cool workflow", + "steps": { + "0": { + "annotation": "Some text", + "content_id": null, + "id": 0, + "input_connections": {}, + "inputs": [ + { + "description": "Some text", + "name": "The cool input" + } + ], + "label": "The cool input", + "name": "Input dataset", + "outputs": [], + "tool_id": null, + "tool_state": "{\"optional\": false, \"tag\": \"\"}", + "tool_version": null, + "type": "data_input", + "workflow_outputs": [ + { + "label": null, + "output_name": "output" + } + ] + }, + "1": { + "annotation": "", + "content_id": "wc_gnu", + "id": 1, + "input_connections": { + "input1": { + "id": 0, + "output_name": "output" + } + }, + "inputs": [ + { + "description": "runtime parameter for tool Line/Word/Character count", + "name": "input1" + } + ], + "label": null, + "name": "Line/Word/Character count", + "outputs": [ + { + "name": "out_file1", + "type": "tabular" + } + ], + "post_job_actions": {}, + "tool_id": "wc_gnu", + "tool_state": "{\"include_header\": \"true\", \"input1\": {\"__class__\": \"RuntimeValue\"}, \"options\": [\"lines\", \"words\", \"characters\"], \"__page__\": null, \"__rerun_remap_job_id__\": null}", + "tool_version": "1.0.0", + "type": "tool", + "workflow_outputs": [ + { + "label": "Text Count Result", + "output_name": "out_file1" + } + ] + } + }, + "tags": [] +} diff --git a/test-data/json/clean/wf_01_dirty.ga b/test-data/json/clean/wf_01_dirty.ga new file mode 100644 index 0000000..ef79de5 --- /dev/null +++ b/test-data/json/clean/wf_01_dirty.ga @@ -0,0 +1,99 @@ +{ + "a_galaxy_workflow": "true", + "annotation": "This is a cool workflow", + "format-version": "0.1", + "license": "MIT", + "name": "Cool workflow", + "steps": { + "0": { + "annotation": "Some text", + "content_id": null, + "errors": null, + "id": 0, + "input_connections": {}, + "inputs": [ + { + "description": "Some text", + "name": "The cool input" + } + ], + "label": "The cool input", + "name": "Input dataset", + "outputs": [], + "position": { + "bottom": 610.28125, + "height": 61.78125, + "left": 824, + "right": 1024, + "top": 548.5, + "width": 200, + "x": 824, + "y": 548.5 + }, + "tool_id": null, + "tool_state": "{\"optional\": false, \"tag\": \"\"}", + "tool_version": null, + "type": "data_input", + "uuid": "f0019f6a-b5ee-476e-8e5f-11cd3bcfaccf", + "workflow_outputs": [ + { + "label": null, + "output_name": "output", + "uuid": "2216c468-98e3-42f9-9174-1e4b65f07e8e" + } + ] + }, + "1": { + "annotation": "", + "content_id": "wc_gnu", + "errors": null, + "id": 1, + "input_connections": { + "input1": { + "id": 0, + "output_name": "output" + } + }, + "inputs": [ + { + "description": "runtime parameter for tool Line/Word/Character count", + "name": "input1" + } + ], + "label": null, + "name": "Line/Word/Character count", + "outputs": [ + { + "name": "out_file1", + "type": "tabular" + } + ], + "position": { + "bottom": 781.453125, + "height": 133.953125, + "left": 1146, + "right": 1346, + "top": 647.5, + "width": 200, + "x": 1146, + "y": 647.5 + }, + "post_job_actions": {}, + "tool_id": "wc_gnu", + "tool_state": "{\"include_header\": \"true\", \"input1\": {\"__class__\": \"RuntimeValue\"}, \"options\": [\"lines\", \"words\", \"characters\"], \"__page__\": null, \"__rerun_remap_job_id__\": null}", + "tool_version": "1.0.0", + "type": "tool", + "uuid": "1ca6f40f-7b65-46ae-a366-9592279a07cc", + "workflow_outputs": [ + { + "label": "Text Count Result", + "output_name": "out_file1", + "uuid": "d9a1c36f-bb05-4dc8-b6d8-10b94ddffb77" + } + ] + } + }, + "tags": [], + "uuid": "1bcc69fb-e8ec-49db-bd60-c7ac7fa87838", + "version": 2 +} diff --git a/test-data/json/validation/test_wf_01.ga b/test-data/json/validation/test_wf_01.ga new file mode 100644 index 0000000..b45ce03 --- /dev/null +++ b/test-data/json/validation/test_wf_01.ga @@ -0,0 +1,87 @@ +{ + "a_galaxy_workflow": "true", + "annotation": "simple workflow", + "format-version": "0.1", + "name": "TestWorkflow1", + "steps": { + "0": { + "annotation": "input1 description", + "id": 0, + "input_connections": {}, + "inputs": [ + { + "description": "input1 description", + "name": "WorkflowInput1" + } + ], + "name": "Input dataset", + "outputs": [], + "position": { + "left": 199.55555772781372, + "top": 200.66666460037231 + }, + "tool_errors": null, + "tool_id": null, + "tool_state": "{\"name\": \"WorkflowInput1\"}", + "tool_version": null, + "type": "data_input", + "user_outputs": [] + }, + "1": { + "annotation": "", + "id": 1, + "input_connections": {}, + "inputs": [ + { + "description": "", + "name": "WorkflowInput2" + } + ], + "name": "Input dataset", + "outputs": [], + "position": { + "left": 206.22221422195435, + "top": 327.33335161209106 + }, + "tool_errors": null, + "tool_id": null, + "tool_state": "{\"name\": \"WorkflowInput2\"}", + "tool_version": null, + "type": "data_input", + "user_outputs": [] + }, + "2": { + "annotation": "", + "id": 2, + "input_connections": { + "input1": { + "id": 0, + "output_name": "output" + }, + "queries_0|input2": { + "id": 1, + "output_name": "output" + } + }, + "inputs": [], + "name": "Concatenate datasets", + "outputs": [ + { + "name": "out_file1", + "type": "input" + } + ], + "position": { + "left": 419.33335876464844, + "top": 200.44446563720703 + }, + "post_job_actions": {}, + "tool_errors": null, + "tool_id": "cat1", + "tool_state": "{\"__page__\": 0, \"__rerun_remap_job_id__\": null, \"input1\": \"null\", \"queries\": \"[{\\\"input2\\\": null, \\\"__index__\\\": 0}]\"}", + "tool_version": "1.0.0", + "type": "tool", + "user_outputs": [] + } + } +} diff --git a/test-data/test_workflow_01.gxwf.yml b/test-data/yaml/test_workflow_01.gxwf.yml similarity index 100% rename from test-data/test_workflow_01.gxwf.yml rename to test-data/yaml/test_workflow_01.gxwf.yml