From 64e95be1761a3d5620d8e5025e8e2d5e308b7937 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 24 Sep 2023 17:02:12 -0700 Subject: [PATCH] Add basic framework and simple UI tests of extension within VS Code via @vscode/test-electron. --- .gitignore | 1 + package.json | 4 +- test/index.cjs | 16 +++++ test/run-tests.mjs | 22 +++++++ test/tests.cjs | 149 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 test/index.cjs create mode 100644 test/run-tests.mjs create mode 100644 test/tests.cjs diff --git a/.gitignore b/.gitignore index 4deb025..35c6f56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode-test .vscode-test-web bundle.js bundle.web.js diff --git a/package.json b/package.json index 46793c2..b86f288 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,10 @@ "compile-debug": "webpack --mode none", "docker-npm-install": "docker run --rm --tty --name npm-install --volume $PWD:/home/workdir --workdir /home/workdir --user node node:latest npm install", "docker-npm-run-upgrade": "docker run --rm --tty --name npm-run-upgrade --volume $PWD:/home/workdir --workdir /home/workdir --user node node:latest npm run upgrade", - "lint": "eslint --ignore-pattern bundle.js --ignore-pattern bundle.web.js *.js && markdownlint-cli2 *.md", + "lint": "eslint --ignore-pattern bundle.js --ignore-pattern bundle.web.js *.js test/*.cjs && markdownlint-cli2 *.md", "schema": "cpy ./node_modules/markdownlint/schema/markdownlint-config-schema.json . --flat && node expand-config-schema.js && node generate-config-schema.js && cpy ./node_modules/markdownlint-cli2/schema/markdownlint-cli2-config-schema.json . --flat", "test": "npm run lint && npm run compile && npm run schema && git diff --exit-code", + "test-ui": "node ./test/run-tests.mjs", "upgrade": "npx --yes npm-check-updates --upgrade" }, "categories": [ @@ -47,6 +48,7 @@ }, "devDependencies": { "@types/vscode": "1.75.0", + "@vscode/test-electron": "2.3.4", "cpy-cli": "5.0.0", "eslint": "8.48.0", "eslint-plugin-node": "11.1.0", diff --git a/test/index.cjs b/test/index.cjs new file mode 100644 index 0000000..73a6386 --- /dev/null +++ b/test/index.cjs @@ -0,0 +1,16 @@ +"use strict"; + +// This must be CommonJS or the VS Code host fails with: +// Error [ERR_REQUIRE_ESM]: require() of ES Module .../index.mjs not supported. + +const {tests} = require("./tests.cjs"); + +function run () { + return tests.reduce((previous, current) => previous.then(() => { + // eslint-disable-next-line no-console + console.log(`${current.name}...`); + return current(); + }), Promise.resolve()); +} + +module.exports = {run}; diff --git a/test/run-tests.mjs b/test/run-tests.mjs new file mode 100644 index 0000000..49a30e9 --- /dev/null +++ b/test/run-tests.mjs @@ -0,0 +1,22 @@ +import { runTests } from "@vscode/test-electron"; + +// @ts-ignore +const extensionDevelopmentPath = new URL("..", import.meta.url).pathname; +// @ts-ignore +const extensionTestsPath = new URL("./index.cjs", import.meta.url).pathname; + +try { + // @ts-ignore + await runTests({ + extensionDevelopmentPath, + extensionTestsPath, + "launchArgs": [ + "--disable-extensions" + // Including "--extensionDevelopmentKind=web" causes the VS Code host to fail with: + // TypeError: Failed to fetch + ] + }); +} catch (error) { + console.error(`TEST FAILURE: ${error}`); + process.exit(1); +} diff --git a/test/tests.cjs b/test/tests.cjs new file mode 100644 index 0000000..75f3010 --- /dev/null +++ b/test/tests.cjs @@ -0,0 +1,149 @@ +"use strict"; + +// This must be CommonJS or the VS Code host fails with: +// Error: Cannot find package 'vscode' imported from .../tests.mjs + +/* eslint-disable func-style */ + +const assert = require("node:assert"); +const path = require("node:path"); +const vscode = require("vscode"); + +function testWrapper (test) { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("TEST TIMEOUT")), 10_000); + const disposables = []; + const cleanup = () => { + clearTimeout(timeout); + for (const disposable of disposables) { + disposable.dispose(); + } + vscode.commands.executeCommand("workbench.action.closeAllEditors"); + }; + const resolveWrapper = (value) => { + resolve(value); + cleanup(); + }; + const rejectWrapper = (reason) => { + reject(reason); + cleanup(); + }; + try { + test(resolveWrapper, rejectWrapper, disposables); + } catch (error) { + rejectWrapper(error); + } + }); +} + +function callbackWrapper (reject, callback) { + try { + return callback(); + } catch (error) { + return reject(error); + } +} + +const tests = [ + + function openLintEditVerifyFixAll () { + return testWrapper((resolve, reject, disposables) => { + let fixedAll = false; + disposables.push( + vscode.window.onDidChangeActiveTextEditor((textEditor) => { + callbackWrapper(reject, () => { + assert.ok(textEditor.document.uri.path.endsWith("/README.md")); + textEditor.edit((editBuilder) => { + // MD019 + editBuilder.insert(new vscode.Position(0, 1), " "); + // MD012 + editBuilder.insert(new vscode.Position(1, 0), "\n"); + }); + }); + }), + vscode.languages.onDidChangeDiagnostics((diagnosticChangeEvent) => { + callbackWrapper(reject, () => { + const {uris} = diagnosticChangeEvent; + assert.equal(uris.length, 1); + const [ uri ] = uris; + const diagnostics = vscode.languages.getDiagnostics(uri); + if ((diagnostics.length > 0) && !fixedAll) { + // eslint-disable-next-line array-element-newline + const [ md019, md012 ] = diagnostics; + // @ts-ignore + assert.equal(md019.code.value, "MD019"); + assert.equal( + // @ts-ignore + md019.code.target.toString(), + "https://github.com/DavidAnson/markdownlint/blob/v0.31.1/doc/md019.md" + ); + assert.equal( + md019.message, + "MD019/no-multiple-space-atx: Multiple spaces after hash on atx style heading" + ); + assert.ok(md019.range.isEqual(new vscode.Range(0, 0, 0, 4))); + assert.equal(md019.severity, vscode.DiagnosticSeverity.Warning); + assert.equal(md019.source, "markdownlint"); + // @ts-ignore + assert.equal(md012.code.value, "MD012"); + assert.equal( + // @ts-ignore + md012.code.target.toString(), + "https://github.com/DavidAnson/markdownlint/blob/v0.31.1/doc/md012.md" + ); + assert.equal( + md012.message, + "MD012/no-multiple-blanks: Multiple consecutive blank lines [Expected: 1; Actual: 2]" + ); + assert.ok(md012.range.isEqual(new vscode.Range(2, 0, 2, 0))); + assert.equal(md012.severity, vscode.DiagnosticSeverity.Warning); + assert.equal(md012.source, "markdownlint"); + vscode.commands.executeCommand("markdownlint.fixAll"); + fixedAll = true; + } else if ((diagnostics.length === 0) && fixedAll) { + resolve(); + } + }); + }) + ); + vscode.window.showTextDocument(vscode.Uri.file(path.join(__dirname, "..", "README.md"))); + }); + }, + + function openLintEditCloseClean () { + return testWrapper((resolve, reject, disposables) => { + let closedActiveEditor = false; + disposables.push( + vscode.window.onDidChangeActiveTextEditor((textEditor) => { + callbackWrapper(reject, () => { + if (textEditor) { + assert.ok(textEditor.document.uri.path.endsWith("/README.md")); + textEditor.edit((editBuilder) => { + editBuilder.insert(new vscode.Position(0, 1), " "); + editBuilder.insert(new vscode.Position(1, 0), "\n"); + }); + } + }); + }), + vscode.languages.onDidChangeDiagnostics((diagnosticChangeEvent) => { + callbackWrapper(reject, () => { + const {uris} = diagnosticChangeEvent; + assert.equal(uris.length, 1); + const [ uri ] = uris; + const diagnostics = vscode.languages.getDiagnostics(uri); + if ((diagnostics.length > 0) && !closedActiveEditor) { + vscode.commands.executeCommand("workbench.action.closeActiveEditor"); + closedActiveEditor = true; + } else if ((diagnostics.length === 0) && closedActiveEditor) { + resolve(); + } + }); + }) + ); + vscode.window.showTextDocument(vscode.Uri.file(path.join(__dirname, "..", "README.md"))); + }); + } + +]; + +module.exports = {tests};