diff --git a/.circleci/config.yml b/.circleci/config.yml index 4449657a6..027b49ec0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -212,7 +212,7 @@ jobs: command: npm run compile-test - run: name: Apt install missing dependencies - command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E88979FB9B30ACF2; sudo apt update && sudo apt install -y libnss3 + command: sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E88979FB9B30ACF2; sudo apt update && sudo apt install -y libnss3 imagemagick - run: name: Pip install Basilisp (Python) command: sudo add-apt-repository --yes ppa:deadsnakes/ppa; sudo apt-get update; sudo apt-get install python3.12; wget https://bootstrap.pypa.io/get-pip.py; sudo python3.12 get-pip.py; sudo python3.12 -m pip install --upgrade pip setuptools wheel; sudo python3.12 -m pip install pipenv; python3.12 -m pip install basilisp==0.1.0b2; @@ -221,6 +221,9 @@ jobs: command: npm run integration-test - store_test_results: path: ~/calva/junit + - store_artifacts: + path: ~/calva/out/extension-test/integration/screenshots + destination: integration-screenshots test-e2e: docker: - image: cimg/clojure:1.11-browsers diff --git a/.circleci/jobs/test-integration.yaml b/.circleci/jobs/test-integration.yaml index 29f6d3c5b..2a3dda942 100644 --- a/.circleci/jobs/test-integration.yaml +++ b/.circleci/jobs/test-integration.yaml @@ -20,7 +20,7 @@ steps: - Apt install missing dependencies - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E88979FB9B30ACF2; - sudo apt update && sudo apt install -y libnss3 + sudo apt update && sudo apt install -y libnss3 imagemagick - !cmd: - Pip install Basilisp (Python) @@ -39,3 +39,7 @@ steps: - !store: test_results: ~/calva/junit + +- store_artifacts: + path: ~/calva/out/extension-test/integration/screenshots + destination: integration-screenshots diff --git a/CHANGELOG.md b/CHANGELOG.md index 8418e1a6c..07d120d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Changes to Calva. ## [Unreleased] +## [2.0.471] - 2024-09-12 + +- [Trim leading newlines from the result/error string before inline display](https://github.com/BetterThanTomorrow/calva/issues/2617) + ## [2.0.470] - 2024-09-11 - Bump deps.clj to v1.12.0.1479 diff --git a/package-lock.json b/package-lock.json index ce8d20e56..16ace3509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "calva", - "version": "2.0.470", + "version": "2.0.471", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "calva", - "version": "2.0.470", + "version": "2.0.471", "license": "MIT", "dependencies": { "@vscode/debugadapter": "^1.64.0", @@ -39,6 +39,7 @@ "@types/lodash": "^4.14.167", "@types/mocha": "^9.1.0", "@types/node": "^18.6.3", + "@types/screenshot-desktop": "^1.12.3", "@types/semver": "^7.3.9", "@types/uuid": "^8.3.4", "@types/vscode": "^1.67.0", @@ -64,10 +65,12 @@ "ovsx": "^0.8.3", "prettier": "2.5.1", "rimraf": "^2.7.1", + "screenshot-desktop": "^1.15.0", "shadow-cljs": "^2.25.2", "source-map-support": "^0.5.16", "ts-loader": "^8.0.18", "ts-node": "^10.3.0", + "ts-sinon": "^2.0.2", "typescript": "^4.6.3", "webpack": "^5.75.0", "webpack-cli": "^5.0.0" @@ -1424,6 +1427,23 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1624,12 +1644,46 @@ "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", "dev": true }, + "node_modules/@types/screenshot-desktop": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@types/screenshot-desktop/-/screenshot-desktop-1.12.3.tgz", + "integrity": "sha512-b1BoY60eEUwXgAE4le7gQFf78RWQxveF/ZKW5Ifh1+M4byPiyp3kP4qStVL+2pwrwaaJyN8qzeaczSDfz2WOPw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, + "node_modules/@types/sinon": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", + "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinon-chai": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", + "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", + "dev": true, + "dependencies": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -6921,6 +6975,12 @@ "set-immediate-shim": "~1.0.1" } }, + "node_modules/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "node_modules/keypress": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz", @@ -7029,6 +7089,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", @@ -7782,6 +7848,28 @@ "resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz", "integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g=" }, + "node_modules/nise": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, "node_modules/node-abi": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", @@ -8292,6 +8380,21 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9029,6 +9132,21 @@ "node": ">=10" } }, + "node_modules/screenshot-desktop": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/screenshot-desktop/-/screenshot-desktop-1.15.0.tgz", + "integrity": "sha512-CLaZNBDEXU+KJ6BGsO8jSbKI7Zck7gQmFJHjzluBdwrVP0jOemP2avpD3ufWu81yqzwB92u2AMv+K9IlaslRsg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/bencevans" + } + ], + "dependencies": { + "temp": "^0.9.4" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -9253,6 +9371,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -9645,6 +9812,32 @@ "node": ">=10" } }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -10026,6 +10219,24 @@ "node": ">=0.3.1" } }, + "node_modules/ts-sinon": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-sinon/-/ts-sinon-2.0.2.tgz", + "integrity": "sha512-Eh6rXPQruACHPn+/e5HsIMaHZa17tGP/scGjUeW5eJ/Levn8hBV6zSP/6QkEDUP7wLkTyY0yeYikjpTzgC9Gew==", + "dev": true, + "dependencies": { + "@types/node": "^14.6.1", + "@types/sinon": "^9.0.5", + "@types/sinon-chai": "^3.2.4", + "sinon": "^9.0.3" + } + }, + "node_modules/ts-sinon/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "dev": true + }, "node_modules/tsconfig-paths": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", @@ -12001,6 +12212,23 @@ "@sinonjs/commons": "^1.7.0" } }, + "@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -12198,12 +12426,46 @@ "integrity": "sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw==", "dev": true }, + "@types/screenshot-desktop": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@types/screenshot-desktop/-/screenshot-desktop-1.12.3.tgz", + "integrity": "sha512-b1BoY60eEUwXgAE4le7gQFf78RWQxveF/ZKW5Ifh1+M4byPiyp3kP4qStVL+2pwrwaaJyN8qzeaczSDfz2WOPw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, + "@types/sinon": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", + "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinon-chai": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz", + "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -16267,6 +16529,12 @@ "set-immediate-shim": "~1.0.1" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "keypress": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz", @@ -16356,6 +16624,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", @@ -16967,6 +17241,30 @@ "resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz", "integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g=" }, + "nise": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, "node-abi": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", @@ -17372,6 +17670,23 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + } + } + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -17946,6 +18261,15 @@ "xmlchars": "^2.2.0" } }, + "screenshot-desktop": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/screenshot-desktop/-/screenshot-desktop-1.15.0.tgz", + "integrity": "sha512-CLaZNBDEXU+KJ6BGsO8jSbKI7Zck7gQmFJHjzluBdwrVP0jOemP2avpD3ufWu81yqzwB92u2AMv+K9IlaslRsg==", + "dev": true, + "requires": { + "temp": "^0.9.4" + } + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -18115,6 +18439,46 @@ } } }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -18428,6 +18792,27 @@ } } }, + "temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -18692,6 +19077,26 @@ } } }, + "ts-sinon": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-sinon/-/ts-sinon-2.0.2.tgz", + "integrity": "sha512-Eh6rXPQruACHPn+/e5HsIMaHZa17tGP/scGjUeW5eJ/Levn8hBV6zSP/6QkEDUP7wLkTyY0yeYikjpTzgC9Gew==", + "dev": true, + "requires": { + "@types/node": "^14.6.1", + "@types/sinon": "^9.0.5", + "@types/sinon-chai": "^3.2.4", + "sinon": "^9.0.3" + }, + "dependencies": { + "@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", diff --git a/package.json b/package.json index e52bf82d8..a8c4121f6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Calva: Clojure & ClojureScript Interactive Programming", "description": "Integrated REPL, formatter, Paredit, and more. Powered by cider-nrepl and clojure-lsp.", "icon": "assets/calva.png", - "version": "2.0.470", + "version": "2.0.471", "publisher": "betterthantomorrow", "author": { "name": "Better Than Tomorrow", @@ -3364,6 +3364,7 @@ "@types/lodash": "^4.14.167", "@types/mocha": "^9.1.0", "@types/node": "^18.6.3", + "@types/screenshot-desktop": "^1.12.3", "@types/semver": "^7.3.9", "@types/uuid": "^8.3.4", "@types/vscode": "^1.67.0", @@ -3389,10 +3390,12 @@ "ovsx": "^0.8.3", "prettier": "2.5.1", "rimraf": "^2.7.1", + "screenshot-desktop": "^1.15.0", "shadow-cljs": "^2.25.2", "source-map-support": "^0.5.16", "ts-loader": "^8.0.18", "ts-node": "^10.3.0", + "ts-sinon": "^2.0.2", "typescript": "^4.6.3", "webpack": "^5.75.0", "webpack-cli": "^5.0.0" diff --git a/src/extension-test/integration/suite/annotations-test.ts b/src/extension-test/integration/suite/annotations-test.ts new file mode 100644 index 000000000..ca2b23eb8 --- /dev/null +++ b/src/extension-test/integration/suite/annotations-test.ts @@ -0,0 +1,66 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +import { before, after, afterEach } from 'mocha'; +import * as sinon from 'sinon'; + +import annotations from '../../../providers/annotations'; +import * as testUtil from './util'; + +suite('Annotations suite', () => { + const suite = 'Annotations'; + + before(() => { + testUtil.showMessage(suite, `suite starting!`); + }); + + after(() => { + testUtil.showMessage(suite, `suite done!`); + }); + + afterEach(function () { + sinon.restore(); // Restore original methods + }); + + test('decorate result trims leading whitespaces', async function () { + testUtil.log(suite, 'activeEditor'); + + // any file would do + const testFilePath = path.join(testUtil.testDataDir, 'test.clj'); + const editor = await testUtil.openFile(testFilePath); + + // Any range will do, the range below happens to be around "(foo)". + const range = new vscode.Range(new vscode.Position(11, 0), new vscode.Position(11, 5)); + + const proxyEditor = testUtil.createVscTextEditorProxy(editor); + + // will eventually call on the editor's `setDecorations` fn with the text to render. + const setDecorationsSpy = sinon.spy(proxyEditor, 'setDecorations'); + + // an error with + annotations.decorateResults( + ' \nLine2 exception message\nLine3 error', + true, + range, + proxyEditor + ); + const args = setDecorationsSpy.firstCall.args as [ + decorationType: vscode.TextEditorDecorationType, + rangesOrOptions: readonly vscode.DecorationOptions[] + ]; + + await testUtil.captureScreenshot(suite, 'decorateResults-empty-1st-line', 1000); + + // The expectation is that all whitespace is removed at the front. + // + // Only Line2 should be displayed in the screenshot above, even though Line3 is still in the text. + assert.strictEqual( + args[1][0].renderOptions.after.contentText, + '\u00A0=>\u00A0Line2\u00A0exception\u00A0message\nLine3\u00A0error\u00A0' + ); + + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + testUtil.log(suite, 'test.clj closed'); + }); +}); diff --git a/src/extension-test/integration/suite/util.ts b/src/extension-test/integration/suite/util.ts index ae60da3ea..cf0f553d6 100644 --- a/src/extension-test/integration/suite/util.ts +++ b/src/extension-test/integration/suite/util.ts @@ -1,6 +1,8 @@ +import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import * as which from 'which'; +import * as screenshot from 'screenshot-desktop'; export const testDataDir = path.join( __dirname, @@ -11,6 +13,9 @@ export const testDataDir = path.join( export const isCircleCI = process.env.CIRCLECI === 'true'; +// The top level directory where screenshots will be stored. +const screenshotsDir = path.join(__dirname, '../screenshots'); + export async function openFile(filePath: string) { const uri = vscode.Uri.file(filePath); const document = await vscode.workspace.openTextDocument(uri); @@ -42,3 +47,60 @@ export function getExecutablePath(executablePathMaybe: string) { return null; } } + +// Captures a screenshot and saves it to +// /SUITE/TESTNAME.png. Waits DELAYMS milliseconds +// before capturing. Returns the file path or null on error. +export async function captureScreenshot( + suite: string, + testName: string, + delayMs: number +): Promise { + let outputPath = path.join(screenshotsDir, suite, `${testName}.png`); + + try { + const dir = path.dirname(outputPath); + fs.mkdirSync(dir, { recursive: true }); + + if (delayMs > 0) { + await sleep(delayMs); + } + + const imageBuffer = await screenshot(); + + fs.writeFileSync(outputPath, imageBuffer); + log(suite, `Screenshot saved to ${outputPath}`); + } catch (error) { + log(suite, 'Error capturing screenshot:', error); + outputPath = null; + } + return outputPath; +} + +// Attempts to creates a wrapper around all functions and properties +// of vscode.TextEditor EDITOR and returns it. This enables `sinon` to +// spy even on read-only properties andfunctions of the EDITOR. +export function createVscTextEditorProxy(editor: vscode.TextEditor): vscode.TextEditor { + const proxyEditor = {} as unknown as vscode.TextEditor; + + Object.keys(editor).forEach((key) => { + const descriptor = Object.getOwnPropertyDescriptor(editor, key); + { + const realValue = (editor as any)[key]; + if (typeof realValue === 'function') { + (proxyEditor as any)[key] = (...args: any[]) => { + return realValue.apply(editor, args); + }; + } else { + Object.defineProperty(proxyEditor, key, { + get: descriptor.get, + set: descriptor.set, + configurable: descriptor.configurable, + enumerable: descriptor.enumerable, + }); + } + } + }); + + return proxyEditor; +} diff --git a/src/providers/annotations.ts b/src/providers/annotations.ts index bdc20bbc0..4b10ef5e0 100644 --- a/src/providers/annotations.ts +++ b/src/providers/annotations.ts @@ -102,7 +102,8 @@ function decorateResults( const uri = editor.document.uri; const key = uri + ':resultDecorationRanges'; let decorationRanges = util.cljsLib.getStateValue(key) || []; - const decoration = evaluated(` => ${resultString} `, resultString, hasError); + const resultTrimmed = resultString.trimStart(); + const decoration = evaluated(` => ${resultTrimmed} `, resultTrimmed, hasError); decorationRanges = _.filter(decorationRanges, (o) => { return !o.codeRange.intersection(codeSelection); });