From ea6c4089e538ceb614c50b652ad988231bb45243 Mon Sep 17 00:00:00 2001 From: Rhys Date: Thu, 8 Dec 2022 13:11:54 -0500 Subject: [PATCH] feat(tree-explorer): add clone document context menu item to document item in tree explorer VSCODE-350 (#458) --- package-lock.json | 164 +++++- package.json | 14 + src/commands/index.ts | 1 + src/editors/playgroundController.ts | 16 +- src/explorer/documentTreeItem.ts | 38 +- src/mdbExtensionController.ts | 16 + .../playgroundCloneDocumentTemplate.ts | 11 + src/test/suite/extension.test.ts | 1 + src/test/suite/mdbExtensionController.test.ts | 529 ++++++++++-------- src/typings.d.ts | 3 + 10 files changed, 534 insertions(+), 259 deletions(-) create mode 100644 src/templates/playgroundCloneDocumentTemplate.ts create mode 100644 src/typings.d.ts diff --git a/package-lock.json b/package-lock.json index 33400e5c6..05f68e5f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "mongodb-connection-string-url": "^2.5.3", "mongodb-data-service": "^22.1.1", "mongodb-ns": "^2.4.0", + "mongodb-query-parser": "^2.4.6", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", "react": "^16.14.0", @@ -7163,6 +7164,28 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ejson-shell-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ejson-shell-parser/-/ejson-shell-parser-1.2.0.tgz", + "integrity": "sha512-2DbCot5/HYcO6kY91nLU5I0CKLTuK9LiKiwaY7OFvfqf3xVgYAvUiLAQKHKeqUFlIUXY3KwRtXfaF5Vic6R1dA==", + "dependencies": { + "acorn": "^8.1.0" + }, + "peerDependencies": { + "bson": "^4.2.3" + } + }, + "node_modules/ejson-shell-parser/node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.73", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.73.tgz", @@ -10636,6 +10659,11 @@ "node": ">=8" } }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==" + }, "node_modules/is-mongodb-running": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-mongodb-running/-/is-mongodb-running-1.0.2.tgz", @@ -10995,6 +11023,11 @@ "node": ">= 4" } }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==" + }, "node_modules/jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", @@ -13476,8 +13509,7 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "engines": [ "node >= 0.2.0" - ], - "optional": true + ] }, "node_modules/jsonpointer.js": { "version": "0.4.0", @@ -13489,7 +13521,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "optional": true, "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -14014,8 +14045,7 @@ "node_modules/lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "optional": true + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", @@ -14090,8 +14120,7 @@ "node_modules/lodash.transform": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", - "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=", - "optional": true + "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=" }, "node_modules/log-symbols": { "version": "4.0.0", @@ -15161,7 +15190,6 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "optional": true, "engines": { "node": "*" } @@ -15730,7 +15758,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/mongodb-extended-json/-/mongodb-extended-json-1.11.0.tgz", "integrity": "sha512-+PLUMH7amvTYumCUR6alR474KmqtlmYeceJjsC+zcfdXls9IotfTp2WIuD6X5tO9dLDVCDqboqjgvXj/JjGj6g==", - "optional": true, "dependencies": { "async": "^3.1.0", "bson": "^1.0.1", @@ -15745,14 +15772,12 @@ "node_modules/mongodb-extended-json/node_modules/async": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "optional": true + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, "node_modules/mongodb-extended-json/node_modules/bson": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", - "optional": true, "engines": { "node": ">=0.6.19" } @@ -15785,6 +15810,11 @@ "debug": "^4.1.1" } }, + "node_modules/mongodb-language-model": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mongodb-language-model/-/mongodb-language-model-1.7.1.tgz", + "integrity": "sha512-qkYggIFWxpC2PiG+lKRjdZ069Q+g0YQ8wYFJRnEesudFA1+0TNFqtrBpshBsvmaT41fwdhS4/1/4ey8Bj4qf7g==" + }, "node_modules/mongodb-log-writer": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.4.tgz", @@ -15798,6 +15828,35 @@ "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.0.tgz", "integrity": "sha512-pCutlP/AU0hcJ/f1h2h3nmn79/gXrQo6088QvPCbdx7SBDkAeGPH4AYu28DgqZzFvsn5mtt2RUpsFicYhjaHow==" }, + "node_modules/mongodb-query-parser": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mongodb-query-parser/-/mongodb-query-parser-2.4.6.tgz", + "integrity": "sha512-R9uU//1IlhPPIGvB4ItMwus3E7nJJtBtrSrTV6wTslwDQfL6bpy9PXcIXVHdDsuJADQZdcvdvpj22VxdfqH2ig==", + "dependencies": { + "bson": "^4.6.1", + "debug": "^4.1.1", + "ejson-shell-parser": "^1.1.3", + "is-json": "^2.0.1", + "javascript-stringify": "^2.0.1", + "lodash": "^4.17.15", + "lru-cache": "^5.1.1", + "mongodb-extended-json": "^1.10.2", + "mongodb-language-model": "^1.6.1" + } + }, + "node_modules/mongodb-query-parser/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mongodb-query-parser/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, "node_modules/mongodb-redact": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/mongodb-redact/-/mongodb-redact-0.2.2.tgz", @@ -28506,6 +28565,21 @@ "safer-buffer": "^2.1.0" } }, + "ejson-shell-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ejson-shell-parser/-/ejson-shell-parser-1.2.0.tgz", + "integrity": "sha512-2DbCot5/HYcO6kY91nLU5I0CKLTuK9LiKiwaY7OFvfqf3xVgYAvUiLAQKHKeqUFlIUXY3KwRtXfaF5Vic6R1dA==", + "requires": { + "acorn": "^8.1.0" + }, + "dependencies": { + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + } + } + }, "electron-to-chromium": { "version": "1.4.73", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.73.tgz", @@ -31293,6 +31367,11 @@ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true }, + "is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==" + }, "is-mongodb-running": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-mongodb-running/-/is-mongodb-running-1.0.2.tgz", @@ -31577,6 +31656,11 @@ "is-object": "^1.0.1" } }, + "javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==" + }, "jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", @@ -33702,8 +33786,7 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "optional": true + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, "jsonpointer.js": { "version": "0.4.0", @@ -33715,7 +33798,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "optional": true, "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -34139,8 +34221,7 @@ "lodash.isfunction": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", - "optional": true + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" }, "lodash.isplainobject": { "version": "4.0.6", @@ -34215,8 +34296,7 @@ "lodash.transform": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", - "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=", - "optional": true + "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=" }, "log-symbols": { "version": "4.0.0", @@ -35057,8 +35137,7 @@ "moment": { "version": "2.29.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "optional": true + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" }, "mongodb": { "version": "4.10.0", @@ -35517,7 +35596,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/mongodb-extended-json/-/mongodb-extended-json-1.11.0.tgz", "integrity": "sha512-+PLUMH7amvTYumCUR6alR474KmqtlmYeceJjsC+zcfdXls9IotfTp2WIuD6X5tO9dLDVCDqboqjgvXj/JjGj6g==", - "optional": true, "requires": { "async": "^3.1.0", "bson": "^1.0.1", @@ -35532,14 +35610,12 @@ "async": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", - "optional": true + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, "bson": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", - "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", - "optional": true + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" } } }, @@ -35573,6 +35649,11 @@ "debug": "^4.1.1" } }, + "mongodb-language-model": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mongodb-language-model/-/mongodb-language-model-1.7.1.tgz", + "integrity": "sha512-qkYggIFWxpC2PiG+lKRjdZ069Q+g0YQ8wYFJRnEesudFA1+0TNFqtrBpshBsvmaT41fwdhS4/1/4ey8Bj4qf7g==" + }, "mongodb-log-writer": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mongodb-log-writer/-/mongodb-log-writer-1.1.4.tgz", @@ -35586,6 +35667,37 @@ "resolved": "https://registry.npmjs.org/mongodb-ns/-/mongodb-ns-2.4.0.tgz", "integrity": "sha512-pCutlP/AU0hcJ/f1h2h3nmn79/gXrQo6088QvPCbdx7SBDkAeGPH4AYu28DgqZzFvsn5mtt2RUpsFicYhjaHow==" }, + "mongodb-query-parser": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mongodb-query-parser/-/mongodb-query-parser-2.4.6.tgz", + "integrity": "sha512-R9uU//1IlhPPIGvB4ItMwus3E7nJJtBtrSrTV6wTslwDQfL6bpy9PXcIXVHdDsuJADQZdcvdvpj22VxdfqH2ig==", + "requires": { + "bson": "^4.6.1", + "debug": "^4.1.1", + "ejson-shell-parser": "^1.1.3", + "is-json": "^2.0.1", + "javascript-stringify": "^2.0.1", + "lodash": "^4.17.15", + "lru-cache": "^5.1.1", + "mongodb-extended-json": "^1.10.2", + "mongodb-language-model": "^1.6.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, "mongodb-redact": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/mongodb-redact/-/mongodb-redact-0.2.2.tgz", diff --git a/package.json b/package.json index 2eb5b92fd..87f9910b1 100644 --- a/package.json +++ b/package.json @@ -413,6 +413,10 @@ "command": "mdb.copyDocumentContentsFromTreeView", "title": "Copy Document" }, + { + "command": "mdb.cloneDocumentFromTreeView", + "title": "Clone Document..." + }, { "command": "mdb.deleteDocumentFromTreeView", "title": "Delete Document..." @@ -613,6 +617,11 @@ "when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem", "group": "2@1" }, + { + "command": "mdb.cloneDocumentFromTreeView", + "when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem", + "group": "2@2" + }, { "command": "mdb.deleteDocumentFromTreeView", "when": "view == mongoDBConnectionExplorer && viewItem == documentTreeItem", @@ -791,6 +800,10 @@ "command": "mdb.copyDocumentContentsFromTreeView", "when": "false" }, + { + "command": "mdb.cloneDocumentFromTreeView", + "when": "false" + }, { "command": "mdb.deleteDocumentFromTreeView", "when": "false" @@ -988,6 +1001,7 @@ "mongodb-connection-string-url": "^2.5.3", "mongodb-data-service": "^22.1.1", "mongodb-ns": "^2.4.0", + "mongodb-query-parser": "^2.4.6", "mongodb-schema": "^9.0.0", "numeral": "^2.0.6", "react": "^16.14.0", diff --git a/src/commands/index.ts b/src/commands/index.ts index 792b2c845..8377284c7 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -61,6 +61,7 @@ enum EXTENSION_COMMANDS { MDB_INSERT_OBJECTID_TO_EDITOR = 'mdb.insertObjectIdToEditor', MDB_GENERATE_OBJECTID_TO_CLIPBOARD = 'mdb.generateObjectIdToClipboard', MDB_COPY_DOCUMENT_CONTENTS_FROM_TREE_VIEW = 'mdb.copyDocumentContentsFromTreeView', + MDB_CLONE_DOCUMENT_FROM_TREE_VIEW = 'mdb.cloneDocumentFromTreeView', MDB_DELETE_DOCUMENT_FROM_TREE_VIEW = 'mdb.deleteDocumentFromTreeView', } diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index bc10cbcdb..42d5bd8b8 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { OutputChannel, ProgressLocation, TextEditor } from 'vscode'; import vm from 'vm'; import ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider'; @@ -15,9 +16,9 @@ import { import ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; import formatError from '../utils/formatError'; import { LanguageServerController } from '../language'; -import { OutputChannel, ProgressLocation, TextEditor } from 'vscode'; import playgroundCreateIndexTemplate from '../templates/playgroundCreateIndexTemplate'; import playgroundCreateCollectionTemplate from '../templates/playgroundCreateCollectionTemplate'; +import playgroundCloneDocumentTemplate from '../templates/playgroundCloneDocumentTemplate'; import { PlaygroundResult, ShellExecuteAllResult, @@ -284,6 +285,19 @@ export default class PlaygroundController { return this._createPlaygroundFileWithContent(content); } + createPlaygroundForCloneDocument( + documentContents: string, + databaseName: string, + collectionName: string + ): Promise { + const content = playgroundCloneDocumentTemplate + .replace('CURRENT_DATABASE', databaseName) + .replace('CURRENT_COLLECTION', collectionName) + .replace('DOCUMENT_CONTENTS', documentContents); + + return this._createPlaygroundFileWithContent(content); + } + async createPlayground(): Promise { const useDefaultTemplate = !!vscode.workspace .getConfiguration('mdb') diff --git a/src/explorer/documentTreeItem.ts b/src/explorer/documentTreeItem.ts index 6bf89448a..161a9871d 100644 --- a/src/explorer/documentTreeItem.ts +++ b/src/explorer/documentTreeItem.ts @@ -3,6 +3,8 @@ import * as vscode from 'vscode'; import type { Document } from 'mongodb'; import type { DataService } from 'mongodb-data-service'; import { promisify } from 'util'; +import { toJSString } from 'mongodb-query-parser'; + import formatError from '../utils/formatError'; export const DOCUMENT_ITEM = 'documentTreeItem'; @@ -56,23 +58,31 @@ export default class DocumentTreeItem return Promise.resolve([]); } + async getDocumentContents(): Promise { + const find = promisify(this.dataService.find.bind(this.dataService)); + const documents = await find( + this.namespace, + { _id: this.documentId }, + { limit: 1 } + ); + + if (!documents || documents.length === 0) { + throw new Error('document not found'); + } + + return documents[0]; + } + async getStringifiedEJSONDocumentContents(): Promise { - try { - const find = promisify(this.dataService.find.bind(this.dataService)); - const documents = await find( - this.namespace, - { _id: this.documentId }, - { limit: 1 } - ); + const document = await this.getDocumentContents(); - if (!documents || documents.length === 0) { - throw new Error('document not found'); - } + return EJSON.stringify(document, undefined, 2); + } - return EJSON.stringify(documents[0], undefined, 2); - } catch (error) { - throw new Error(formatError(error).message); - } + async getJSStringDocumentContents(): Promise { + const ejsonDocument = await this.getDocumentContents(); + + return toJSString(ejsonDocument, 2); } async onDeleteDocumentClicked(): Promise { diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 5d414d36b..418f50b2b 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -561,6 +561,22 @@ export default class MDBExtensionController implements vscode.Disposable { return true; } ); + this.registerCommand( + EXTENSION_COMMANDS.MDB_CLONE_DOCUMENT_FROM_TREE_VIEW, + async (documentTreeItem: DocumentTreeItem): Promise => { + const documentContents = + await documentTreeItem.getJSStringDocumentContents(); + + const [databaseName, collectionName] = + documentTreeItem.namespace.split(/\.(.*)/s); + + return this._playgroundController.createPlaygroundForCloneDocument( + documentContents, + databaseName, + collectionName + ); + } + ); this.registerCommand( EXTENSION_COMMANDS.MDB_DELETE_DOCUMENT_FROM_TREE_VIEW, async (documentTreeItem: DocumentTreeItem): Promise => { diff --git a/src/templates/playgroundCloneDocumentTemplate.ts b/src/templates/playgroundCloneDocumentTemplate.ts new file mode 100644 index 000000000..b31770f48 --- /dev/null +++ b/src/templates/playgroundCloneDocumentTemplate.ts @@ -0,0 +1,11 @@ +const template = `// MongoDB Playground +// Use Ctrl+Space inside a snippet or a string literal to trigger completions. + +// The current database to use. +use('CURRENT_DATABASE'); + +// Create a new document in the collection. +db.getCollection('CURRENT_COLLECTION').insertOne(DOCUMENT_CONTENTS); +`; + +export default template; diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index b6042e480..3654e1d48 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -55,6 +55,7 @@ suite('Extension Test Suite', () => { 'mdb.openMongoDBDocumentFromTree', 'mdb.openMongoDBDocumentFromCodeLens', 'mdb.copyDocumentContentsFromTreeView', + 'mdb.cloneDocumentFromTreeView', 'mdb.deleteDocumentFromTreeView', // Editor commands. diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 053c11d8b..170757a10 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { afterEach, beforeEach } from 'mocha'; import assert from 'assert'; import { DataService } from 'mongodb-data-service'; +import { ObjectId } from 'mongodb'; import sinon, { SinonSpy } from 'sinon'; import { @@ -28,7 +29,7 @@ const testDatabaseURI = 'mongodb://localhost:27018'; suite('MDBExtensionController Test Suite', function () { this.timeout(10000); - const sandbox: any = sinon.createSandbox(); + const sandbox = sinon.createSandbox(); let fakeShowInformationMessage: sinon.SinonStub; beforeEach(() => { @@ -46,10 +47,10 @@ suite('MDBExtensionController Test Suite', function () { }); test('mdb.viewCollectionDocuments command should call onViewCollectionDocuments on the editor controller with the collection namespace', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('magna carta'); + const mockOpenTextDocument = sinon.fake.resolves('magna carta'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); const textCollectionTree = new CollectionTreeItem( @@ -68,30 +69,31 @@ suite('MDBExtensionController Test Suite', function () { 'mdb.viewCollectionDocuments', textCollectionTree ); - assert( - mockOpenTextDocument.firstArg.path.indexOf( + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].path.indexOf( 'Results: testDbName.testColName' - ) === 0 + ), + 0 + ); + assert(mockOpenTextDocument.firstCall.args[0].path.includes('.json')); + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].scheme, + VIEW_COLLECTION_SCHEME ); - assert(mockOpenTextDocument.firstArg.path.includes('.json')); - assert(mockOpenTextDocument.firstArg.scheme === VIEW_COLLECTION_SCHEME); assert( - mockOpenTextDocument.firstArg.query.includes( + mockOpenTextDocument.firstCall.args[0].query.includes( 'namespace=testDbName.testColName' ) ); - assert( - mockShowTextDocument.firstArg === 'magna carta', - 'Expected it to call vscode to show the returned documents from the provider' - ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'magna carta'); }); test('mdb.viewCollectionDocuments command should also work with the documents list', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('magna carta'); + const mockOpenTextDocument = sinon.fake.resolves('magna carta'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); const textCollectionTree = new CollectionTreeItem( @@ -110,27 +112,28 @@ suite('MDBExtensionController Test Suite', function () { 'mdb.viewCollectionDocuments', textCollectionTree ); - assert( - mockOpenTextDocument.firstArg.path.indexOf( + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].path.indexOf( 'Results: testDbName.testColName' - ) === 0 + ), + 0 + ); + assert(mockOpenTextDocument.firstCall.args[0].path.includes('.json')); + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].scheme, + VIEW_COLLECTION_SCHEME ); - assert(mockOpenTextDocument.firstArg.path.includes('.json')); - assert(mockOpenTextDocument.firstArg.scheme === VIEW_COLLECTION_SCHEME); assert( - mockOpenTextDocument.firstArg.query.includes( + mockOpenTextDocument.firstCall.args[0].query.includes( 'namespace=testDbName.testColName' ) ); - assert( - mockShowTextDocument.firstArg === 'magna carta', - 'Expected it to call vscode to show the returned documents from the provider' - ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'magna carta'); }); test('mdb.addConnection command should call openWebview on the webview controller', async () => { - const mockOpenWebview: any = sinon.fake(); + const mockOpenWebview = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._webviewController, 'openWebview', @@ -138,14 +141,11 @@ suite('MDBExtensionController Test Suite', function () { ); await vscode.commands.executeCommand('mdb.addConnection'); - assert( - mockOpenWebview.called, - 'Expected "mockOpenWebview" to be called on the webview controller.' - ); + assert.strictEqual(mockOpenWebview.calledOnce, true); }); test('mdb.addConnectionWithURI command should call connectWithURI on the connection controller', async () => { - const mockConnectWithUri: any = sinon.fake(); + const mockConnectWithUri = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'connectWithURI', @@ -153,10 +153,7 @@ suite('MDBExtensionController Test Suite', function () { ); await vscode.commands.executeCommand('mdb.addConnectionWithURI'); - assert( - mockConnectWithUri.called, - 'Expected "connectWithURI" to be called on the connection controller.' - ); + assert.strictEqual(mockConnectWithUri.calledOnce, true); }); test('mdb.refreshConnection command should reset the cache on a connection tree item', async () => { @@ -171,7 +168,7 @@ suite('MDBExtensionController Test Suite', function () { mockTreeItem.cacheIsUpToDate = true; - const mockExplorerControllerRefresh: any = sinon.fake(); + const mockExplorerControllerRefresh = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._explorerController, 'refresh', @@ -201,7 +198,7 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const mockRemoveMongoDBConnection: any = sinon.fake(); + const mockRemoveMongoDBConnection = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'removeMongoDBConnection', @@ -212,14 +209,10 @@ suite('MDBExtensionController Test Suite', function () { 'mdb.treeItemRemoveConnection', mockTreeItem ); - assert( - mockRemoveMongoDBConnection.called, - 'Expected "removeMongoDBConnection" to be called on the connection controller.' - ); - assert( - mockRemoveMongoDBConnection.firstArg === - 'craving_for_pancakes_with_maple_syrup', - `Expected the mock connection controller to be called to remove the connection with the id "craving_for_pancakes_with_maple_syrup", found ${mockRemoveMongoDBConnection.firstArg}.` + assert.strictEqual(mockRemoveMongoDBConnection.calledOnce, true); + assert.strictEqual( + mockRemoveMongoDBConnection.firstCall.args[0], + 'craving_for_pancakes_with_maple_syrup' ); }); @@ -233,13 +226,13 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const mockCopyToClipboard: any = sinon.fake(); + const mockCopyToClipboard = sinon.fake(); sinon.replaceGetter(vscode.env, 'clipboard', () => ({ writeText: mockCopyToClipboard, readText: sinon.fake() as any, })); - const mockStubUri: any = sinon.fake.returns('weStubThisUri'); + const mockStubUri = sinon.fake.returns('weStubThisUri'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'copyConnectionStringByConnectionId', @@ -250,14 +243,8 @@ suite('MDBExtensionController Test Suite', function () { 'mdb.copyConnectionString', mockTreeItem ); - assert( - mockCopyToClipboard.called, - 'Expected "writeText" to be called on "vscode.env.clipboard".' - ); - assert( - mockCopyToClipboard.firstArg === 'weStubThisUri', - `Expected the clipboard to be sent the uri string "weStubThisUri", found ${mockCopyToClipboard.firstArg}.` - ); + assert.strictEqual(mockCopyToClipboard.calledOnce, true); + assert.strictEqual(mockCopyToClipboard.firstCall.args[0], 'weStubThisUri'); }); test('mdb.copyDatabaseName command should try to copy the database name to the vscode env clipboard', async () => { @@ -269,20 +256,17 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const mockCopyToClipboard: any = sinon.fake(); + const mockCopyToClipboard = sinon.fake(); sinon.replaceGetter(vscode.env, 'clipboard', () => ({ writeText: mockCopyToClipboard, readText: sinon.fake() as any, })); await vscode.commands.executeCommand('mdb.copyDatabaseName', mockTreeItem); - assert( - mockCopyToClipboard.called, - 'Expected "writeText" to be called on "vscode.env.clipboard".' - ); - assert( - mockCopyToClipboard.firstArg === 'isClubMateTheBestDrinkEver', - `Expected the clipboard to be sent the uri string "isClubMateTheBestDrinkEver", found ${mockCopyToClipboard.firstArg}.` + assert.strictEqual(mockCopyToClipboard.calledOnce, true); + assert.strictEqual( + mockCopyToClipboard.firstCall.args[0], + 'isClubMateTheBestDrinkEver' ); }); @@ -299,7 +283,7 @@ suite('MDBExtensionController Test Suite', function () { null ); - const mockCopyToClipboard: any = sinon.fake(); + const mockCopyToClipboard = sinon.fake(); sinon.replaceGetter(vscode.env, 'clipboard', () => ({ writeText: mockCopyToClipboard, readText: sinon.fake() as any, @@ -314,8 +298,8 @@ suite('MDBExtensionController Test Suite', function () { 'Expected "writeText" to be called on "vscode.env.clipboard".' ); assert( - mockCopyToClipboard.firstArg === 'waterBuffalo', - `Expected the clipboard to be sent the uri string "waterBuffalo", found ${mockCopyToClipboard.firstArg}.` + mockCopyToClipboard.firstCall.args[0] === 'waterBuffalo', + `Expected the clipboard to be sent the uri string "waterBuffalo", found ${mockCopyToClipboard.firstCall.args[0]}.` ); }); @@ -331,7 +315,7 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const mockCopyToClipboard: any = sinon.fake(); + const mockCopyToClipboard = sinon.fake(); sinon.replaceGetter(vscode.env, 'clipboard', () => ({ writeText: mockCopyToClipboard, readText: sinon.fake() as any, @@ -348,8 +332,8 @@ suite('MDBExtensionController Test Suite', function () { 'Expected "writeText" to be called on "vscode.env.clipboard".' ); assert( - mockCopyToClipboard.firstArg === 'dolphins are sentient', - `Expected the clipboard to be sent the schema field name "dolphins are sentient", found ${mockCopyToClipboard.firstArg}.` + mockCopyToClipboard.firstCall.args[0] === 'dolphins are sentient', + `Expected the clipboard to be sent the schema field name "dolphins are sentient", found ${mockCopyToClipboard.firstCall.args[0]}.` ); }); @@ -364,7 +348,7 @@ suite('MDBExtensionController Test Suite', function () { mockTreeItem.cacheIsUpToDate = true; - const mockExplorerControllerRefresh: any = sinon.fake(); + const mockExplorerControllerRefresh = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._explorerController, 'refresh', @@ -402,7 +386,7 @@ suite('MDBExtensionController Test Suite', function () { mockTreeItem.getSchemaChild().isExpanded = true; mockTreeItem.getDocumentListChild().isExpanded = true; - const mockExplorerControllerRefresh: any = sinon.fake(); + const mockExplorerControllerRefresh = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._explorerController, 'refresh', @@ -445,7 +429,7 @@ suite('MDBExtensionController Test Suite', function () { docListTreeItem.isExpanded = true; - const mockExplorerControllerRefresh: any = sinon.fake(); + const mockExplorerControllerRefresh = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._explorerController, 'refresh', @@ -486,7 +470,7 @@ suite('MDBExtensionController Test Suite', function () { // Set cached. mockTreeItem.cacheIsUpToDate = true; - const mockExplorerControllerRefresh: any = sinon.fake(); + const mockExplorerControllerRefresh = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._explorerController, 'refresh', @@ -517,7 +501,7 @@ suite('MDBExtensionController Test Suite', function () { // Set cached. mockTreeItem.cacheIsUpToDate = true; - const mockExplorerControllerRefresh: any = sinon.fake(); + const mockExplorerControllerRefresh = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._explorerController, 'refresh', @@ -545,7 +529,7 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const fakeVscodeErrorMessage: any = sinon.fake(); + const fakeVscodeErrorMessage = sinon.fake(); sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); const addDatabaseSucceeded = await vscode.commands.executeCommand( @@ -559,8 +543,8 @@ suite('MDBExtensionController Test Suite', function () { const expectedMessage = 'Please connect to this connection before adding a database.'; assert( - fakeVscodeErrorMessage.firstArg === expectedMessage, - `Expected an error message "${expectedMessage}" to be shown when attempting to add a database to a not connected connection found "${fakeVscodeErrorMessage.firstArg}"` + fakeVscodeErrorMessage.firstCall.args[0] === expectedMessage, + `Expected an error message "${expectedMessage}" to be shown when attempting to add a database to a not connected connection found "${fakeVscodeErrorMessage.firstCall.args[0]}"` ); }); @@ -574,30 +558,36 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const mockActiveConnectionId: any = sinon.fake.returns('tasty_sandwhich'); + const mockActiveConnectionId = sinon.fake.returns('tasty_sandwhich'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveConnectionId', mockActiveConnectionId ); - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); await vscode.commands.executeCommand('mdb.addDatabase', mockTreeItem); - assert(mockOpenTextDocument.firstArg.language === 'mongodb'); + assert(mockOpenTextDocument.firstCall.args[0].language === 'mongodb'); assert( - mockOpenTextDocument.firstArg.content.includes( + mockOpenTextDocument.firstCall.args[0].content.includes( '// Create a new database.' ) ); - assert(mockOpenTextDocument.firstArg.content.includes('NEW_DATABASE_NAME')); assert( - mockOpenTextDocument.firstArg.content.includes('NEW_COLLECTION_NAME') + mockOpenTextDocument.firstCall.args[0].content.includes( + 'NEW_DATABASE_NAME' + ) + ); + assert( + mockOpenTextDocument.firstCall.args[0].content.includes( + 'NEW_COLLECTION_NAME' + ) ); }); @@ -616,17 +606,17 @@ suite('MDBExtensionController Test Suite', function () { mockInputBoxResolves.onCall(1).resolves('theCollectionName'); sinon.replace(vscode.window, 'showInputBox', mockInputBoxResolves); - const mockIsDisconnecting: any = sinon.fake.returns(true); + const mockIsDisconnecting = sinon.fake.returns(true); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'isDisconnecting', mockIsDisconnecting ); - const fakeVscodeErrorMessage: any = sinon.fake(); + const fakeVscodeErrorMessage = sinon.fake(); sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); - const mockActiveConnectionId: any = sinon.fake.returns('tasty_sandwhich'); + const mockActiveConnectionId = sinon.fake.returns('tasty_sandwhich'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveConnectionId', @@ -643,8 +633,8 @@ suite('MDBExtensionController Test Suite', function () { ); const expectedMessage = 'Unable to add database: currently disconnecting.'; assert( - fakeVscodeErrorMessage.firstArg === expectedMessage, - `Expected the error message "${expectedMessage}" to be shown when attempting to add a database while disconnecting, found "${fakeVscodeErrorMessage.firstArg}"` + fakeVscodeErrorMessage.firstCall.args[0] === expectedMessage, + `Expected the error message "${expectedMessage}" to be shown when attempting to add a database while disconnecting, found "${fakeVscodeErrorMessage.firstCall.args[0]}"` ); }); @@ -663,16 +653,16 @@ suite('MDBExtensionController Test Suite', function () { mockInputBoxResolves.onCall(1).resolves('theCollectionName'); sinon.replace(vscode.window, 'showInputBox', mockInputBoxResolves); - const mockIsConnecting: any = sinon.fake.returns(true); + const mockIsConnecting = sinon.fake.returns(true); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'isConnecting', mockIsConnecting ); - const fakeVscodeErrorMessage: any = sinon.fake(); + const fakeVscodeErrorMessage = sinon.fake(); sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); - const mockActiveConnectionId: any = sinon.fake.returns('tasty_sandwhich'); + const mockActiveConnectionId = sinon.fake.returns('tasty_sandwhich'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveConnectionId', @@ -689,8 +679,8 @@ suite('MDBExtensionController Test Suite', function () { ); const expectedMessage = 'Unable to add database: currently connecting.'; assert( - fakeVscodeErrorMessage.firstArg === expectedMessage, - `Expected the error message "${expectedMessage}" to be shown when attempting to add a database while disconnecting, found "${fakeVscodeErrorMessage.firstArg}"` + fakeVscodeErrorMessage.firstCall.args[0] === expectedMessage, + `Expected the error message "${expectedMessage}" to be shown when attempting to add a database while disconnecting, found "${fakeVscodeErrorMessage.firstCall.args[0]}"` ); }); @@ -703,32 +693,38 @@ suite('MDBExtensionController Test Suite', function () { {} ); - const mockActiveConnectionId: any = sinon.fake.returns('tasty_sandwhich'); + const mockActiveConnectionId = sinon.fake.returns('tasty_sandwhich'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveConnectionId', mockActiveConnectionId ); - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); await vscode.commands.executeCommand('mdb.addCollection', mockTreeItem); - assert(mockOpenTextDocument.firstArg.language === 'mongodb'); + assert(mockOpenTextDocument.firstCall.args[0].language === 'mongodb'); assert( - mockOpenTextDocument.firstArg.content.includes( + mockOpenTextDocument.firstCall.args[0].content.includes( '// The current database to use.' ) ); - assert(mockOpenTextDocument.firstArg.content.includes('iceCreamDB')); assert( - mockOpenTextDocument.firstArg.content.includes('NEW_COLLECTION_NAME') + mockOpenTextDocument.firstCall.args[0].content.includes('iceCreamDB') + ); + assert( + mockOpenTextDocument.firstCall.args[0].content.includes( + 'NEW_COLLECTION_NAME' + ) + ); + assert( + !mockOpenTextDocument.firstCall.args[0].content.includes('time-series') ); - assert(!mockOpenTextDocument.firstArg.content.includes('time-series')); }); test('mdb.addCollection command fails when disconnecting', async () => { @@ -744,14 +740,14 @@ suite('MDBExtensionController Test Suite', function () { mockInputBoxResolves.onCall(0).resolves('mintChocolateChips'); sinon.replace(vscode.window, 'showInputBox', mockInputBoxResolves); - const mockIsDisconnecting: any = sinon.fake.returns(true); + const mockIsDisconnecting = sinon.fake.returns(true); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'isDisconnecting', mockIsDisconnecting ); - const fakeVscodeErrorMessage: any = sinon.fake(); + const fakeVscodeErrorMessage = sinon.fake(); sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); const addCollectionSucceeded = await vscode.commands.executeCommand( @@ -765,8 +761,8 @@ suite('MDBExtensionController Test Suite', function () { const expectedMessage = 'Unable to add collection: currently disconnecting.'; assert( - fakeVscodeErrorMessage.firstArg === expectedMessage, - `Expected "${expectedMessage}" when adding a database to a not connected connection, recieved "${fakeVscodeErrorMessage.firstArg}"` + fakeVscodeErrorMessage.firstCall.args[0] === expectedMessage, + `Expected "${expectedMessage}" when adding a database to a not connected connection, recieved "${fakeVscodeErrorMessage.firstCall.args[0]}"` ); }); @@ -820,7 +816,7 @@ suite('MDBExtensionController Test Suite', function () { mockInputBoxResolves.onCall(0).resolves('doesntExistColName'); sinon.replace(vscode.window, 'showInputBox', mockInputBoxResolves); - const fakeVscodeErrorMessage: any = sinon.fake(); + const fakeVscodeErrorMessage = sinon.fake(); sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); const successfullyDropped = await vscode.commands.executeCommand( @@ -833,8 +829,8 @@ suite('MDBExtensionController Test Suite', function () { ); const expectedMessage = 'Drop collection failed: ns not found'; assert( - fakeVscodeErrorMessage.firstArg === expectedMessage, - `Expected "${expectedMessage}" when dropping a collection that doesn't exist, recieved "${fakeVscodeErrorMessage.firstArg}"` + fakeVscodeErrorMessage.firstCall.args[0] === expectedMessage, + `Expected "${expectedMessage}" when dropping a collection that doesn't exist, recieved "${fakeVscodeErrorMessage.firstCall.args[0]}"` ); await testConnectionController.disconnect(); @@ -935,7 +931,7 @@ suite('MDBExtensionController Test Suite', function () { mockInputBoxResolves.onCall(0).resolves('narnia____a'); sinon.replace(vscode.window, 'showInputBox', mockInputBoxResolves); - const fakeVscodeErrorMessage: any = sinon.fake(); + const fakeVscodeErrorMessage = sinon.fake(); sinon.replace(vscode.window, 'showErrorMessage', fakeVscodeErrorMessage); const successfullyDropped = await vscode.commands.executeCommand( @@ -1080,13 +1076,13 @@ suite('MDBExtensionController Test Suite', function () { }, }; - const mockOpenTextDocument: any = sinon.fake.resolves('magna carta'); + const mockOpenTextDocument = sinon.fake.resolves('magna carta'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); - const mockGet: any = sinon.fake.returns('pancakes'); + const mockGet = sinon.fake.returns('pancakes'); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._documentIdStore, @@ -1112,14 +1108,14 @@ suite('MDBExtensionController Test Suite', function () { () => activeTextEditor ); - const mockActiveConnectionId: any = sinon.fake.returns('tasty_sandwhich'); + const mockActiveConnectionId = sinon.fake.returns('tasty_sandwhich'); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveConnectionId', mockActiveConnectionId ); - const mockGetActiveDataService: any = sinon.fake.returns({ + const mockGetActiveDataService = sinon.fake.returns({ find: ( namespace: string, filter: object, @@ -1159,18 +1155,26 @@ suite('MDBExtensionController Test Suite', function () { documentItem ); - assert(mockOpenTextDocument.firstArg.path.includes('.json')); - assert(mockOpenTextDocument.firstArg.scheme === 'VIEW_DOCUMENT_SCHEME'); - assert(mockOpenTextDocument.firstArg.query.includes('documentId=')); - assert(mockOpenTextDocument.firstArg.query.includes('connectionId=')); - assert(mockOpenTextDocument.firstArg.query.includes('source=treeview')); + assert(mockOpenTextDocument.firstCall.args[0].path.includes('.json')); + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].scheme, + 'VIEW_DOCUMENT_SCHEME' + ); + assert( + mockOpenTextDocument.firstCall.args[0].query.includes('documentId=') + ); + assert( + mockOpenTextDocument.firstCall.args[0].query.includes('connectionId=') + ); assert( - mockOpenTextDocument.firstArg.query.includes('namespace=waffle.house') + mockOpenTextDocument.firstCall.args[0].query.includes('source=treeview') ); assert( - mockShowTextDocument.firstArg === 'magna carta', - 'Expected it to call vscode to show the returned document from the provider' + mockOpenTextDocument.firstCall.args[0].query.includes( + 'namespace=waffle.house' + ) ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'magna carta'); await vscode.commands.executeCommand('mdb.saveMongoDBDocument'); @@ -1181,7 +1185,7 @@ suite('MDBExtensionController Test Suite', function () { "The document was saved successfully to 'waffle.house'"; assert.strictEqual( - fakeShowInformationMessage.firstCall.firstArg, + fakeShowInformationMessage.firstCall.args[0], expectedMessage ); }); @@ -1202,7 +1206,7 @@ suite('MDBExtensionController Test Suite', function () { () => Promise.resolve() ); - const mockFetchDocument: any = sinon.fake.resolves(null); + const mockFetchDocument = sinon.fake.resolves(null); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._mongoDBDocumentService, @@ -1215,7 +1219,7 @@ suite('MDBExtensionController Test Suite', function () { documentItem ); - assert(mockFetchDocument.firstArg.source === 'treeview'); + assert.strictEqual(mockFetchDocument.firstCall.args[0].source, 'treeview'); }); test('document opened from playground results has treeview source', async () => { @@ -1227,7 +1231,7 @@ suite('MDBExtensionController Test Suite', function () { connectionId: null, }; - const mockFetchDocument: any = sinon.fake.resolves(null); + const mockFetchDocument = sinon.fake.resolves(null); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._mongoDBDocumentService, @@ -1240,7 +1244,10 @@ suite('MDBExtensionController Test Suite', function () { documentItem ); - assert(mockFetchDocument.firstArg.source === 'playground'); + assert.strictEqual( + mockFetchDocument.firstCall.args[0].source, + 'playground' + ); }); test('fetchDocument recieves treeview source if document opened from a tree', async () => { @@ -1252,10 +1259,10 @@ suite('MDBExtensionController Test Suite', function () { }, }; - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); - const mockGet: any = sinon.fake.returns('pancakes'); + const mockGet = sinon.fake.returns('pancakes'); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._documentIdStore, @@ -1263,23 +1270,28 @@ suite('MDBExtensionController Test Suite', function () { mockGet ); - sandbox.replaceGetter(vscode.window, 'activeTextEditor', () => ({ - document: { - uri: { - scheme: 'VIEW_DOCUMENT_SCHEME', - query: [ - 'namespace=waffle.house', - 'connectionId=tasty_sandwhich', - 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a', - 'source=treeview', - ].join('&'), - }, - getText: () => JSON.stringify(mockDocument), - save: () => {}, - }, - })); - - const mockReplaceDocument: any = sinon.fake.resolves(null); + sandbox.replaceGetter( + vscode.window, + 'activeTextEditor', + () => + ({ + document: { + uri: { + scheme: 'VIEW_DOCUMENT_SCHEME', + query: [ + 'namespace=waffle.house', + 'connectionId=tasty_sandwhich', + 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a', + 'source=treeview', + ].join('&'), + }, + getText: () => JSON.stringify(mockDocument), + save: () => {}, + }, + } as any) + ); + + const mockReplaceDocument = sinon.fake.resolves(null); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._mongoDBDocumentService, @@ -1289,7 +1301,10 @@ suite('MDBExtensionController Test Suite', function () { await vscode.commands.executeCommand('mdb.saveMongoDBDocument'); - assert(mockReplaceDocument.firstArg.source === 'treeview'); + assert.strictEqual( + mockReplaceDocument.firstCall.args[0].source, + 'treeview' + ); }); test('fetchDocument recieves playground source if document opened from playground results', async () => { @@ -1301,10 +1316,10 @@ suite('MDBExtensionController Test Suite', function () { }, }; - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); - const mockGet: any = sinon.fake.returns('pancakes'); + const mockGet = sinon.fake.returns('pancakes'); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._documentIdStore, @@ -1312,23 +1327,28 @@ suite('MDBExtensionController Test Suite', function () { mockGet ); - sandbox.replaceGetter(vscode.window, 'activeTextEditor', () => ({ - document: { - uri: { - scheme: 'VIEW_DOCUMENT_SCHEME', - query: [ - 'namespace=waffle.house', - 'connectionId=tasty_sandwhich', - 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a', - 'source=playground', - ].join('&'), - }, - getText: () => JSON.stringify(mockDocument), - save: () => {}, - }, - })); - - const mockReplaceDocument: any = sinon.fake.resolves(null); + sandbox.replaceGetter( + vscode.window, + 'activeTextEditor', + () => + ({ + document: { + uri: { + scheme: 'VIEW_DOCUMENT_SCHEME', + query: [ + 'namespace=waffle.house', + 'connectionId=tasty_sandwhich', + 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a', + 'source=playground', + ].join('&'), + }, + getText: () => JSON.stringify(mockDocument), + save: () => {}, + }, + } as any) + ); + + const mockReplaceDocument = sinon.fake.resolves(null); sinon.replace( mdbTestExtension.testExtensionController._editorsController ._mongoDBDocumentService, @@ -1338,14 +1358,17 @@ suite('MDBExtensionController Test Suite', function () { await vscode.commands.executeCommand('mdb.saveMongoDBDocument'); - assert(mockReplaceDocument.firstArg.source === 'playground'); + assert.strictEqual( + mockReplaceDocument.firstCall.args[0].source, + 'playground' + ); }); test('mdb.searchForDocuments should create a MongoDB playground with search template', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); await vscode.commands.executeCommand('mdb.searchForDocuments', { @@ -1353,25 +1376,29 @@ suite('MDBExtensionController Test Suite', function () { collectionName: 'colllllllllName', }); - assert(mockOpenTextDocument.firstArg.language === 'mongodb'); + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].language, + 'mongodb' + ); assert( - mockOpenTextDocument.firstArg.content.includes( + mockOpenTextDocument.firstCall.args[0].content.includes( 'Search for documents in the current collection.' ) ); - assert(mockOpenTextDocument.firstArg.content.includes('dbbbbbName')); - assert(mockOpenTextDocument.firstArg.content.includes('colllllllllName')); assert( - mockShowTextDocument.firstArg === 'untitled', - 'Expected it to call vscode to show the playground' + mockOpenTextDocument.firstCall.args[0].content.includes('dbbbbbName') ); + assert( + mockOpenTextDocument.firstCall.args[0].content.includes('colllllllllName') + ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'untitled'); }); test('mdb.createIndexFromTreeView should create a MongoDB playground with index template', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); await vscode.commands.executeCommand('mdb.createIndexFromTreeView', { @@ -1379,83 +1406,89 @@ suite('MDBExtensionController Test Suite', function () { collectionName: 'colllllllllName', }); - assert(mockOpenTextDocument.firstArg.language === 'mongodb'); - assert(mockOpenTextDocument.firstArg.content.includes('dbbbbbName')); - assert(mockOpenTextDocument.firstArg.content.includes('colllllllllName')); + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].language, + 'mongodb' + ); assert( - mockOpenTextDocument.firstArg.content.includes( - 'Create a new index in the collection.' - ) + mockOpenTextDocument.firstCall.args[0].content.includes('dbbbbbName') + ); + assert( + mockOpenTextDocument.firstCall.args[0].content.includes('colllllllllName') ); assert( - mockShowTextDocument.firstArg === 'untitled', - 'Expected it to call vscode to show the playground' + mockOpenTextDocument.firstCall.args[0].content.includes( + 'Create a new index in the collection.' + ) ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'untitled'); }); test('mdb.createPlayground should create a MongoDB playground with default template', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); - const mockGetConfiguration: any = sinon.fake.returns({ + const mockGetConfiguration = sinon.fake.returns({ get: () => true, }); sinon.replace(vscode.workspace, 'getConfiguration', mockGetConfiguration); await vscode.commands.executeCommand('mdb.createPlayground'); - assert.strictEqual(mockOpenTextDocument.firstArg.language, 'mongodb'); - assert( - mockOpenTextDocument.firstArg.content.startsWith('// MongoDB Playground') + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].language, + 'mongodb' ); assert( - mockShowTextDocument.firstArg === 'untitled', - 'Expected it to call vscode to show the playground' + mockOpenTextDocument.firstCall.args[0].content.startsWith( + '// MongoDB Playground' + ) ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'untitled'); }); test('mdb.createNewPlaygroundFromViewAction should create a MongoDB playground', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); await vscode.commands.executeCommand('mdb.createPlayground'); - assert.strictEqual(mockOpenTextDocument.firstArg.language, 'mongodb'); - assert( - mockShowTextDocument.firstArg === 'untitled', - 'Expected it to call vscode to show the playground' + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].language, + 'mongodb' ); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'untitled'); }); test('mdb.createPlayground command should create a MongoDB playground without template', async () => { - const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); + const mockOpenTextDocument = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); - const mockShowTextDocument: any = sinon.fake(); + const mockShowTextDocument = sinon.fake(); sinon.replace(vscode.window, 'showTextDocument', mockShowTextDocument); - const mockGetConfiguration: any = sinon.fake.returns({ + const mockGetConfiguration = sinon.fake.returns({ get: () => false, }); sinon.replace(vscode.workspace, 'getConfiguration', mockGetConfiguration); await vscode.commands.executeCommand('mdb.createPlayground'); - assert.strictEqual(mockOpenTextDocument.firstArg.language, 'mongodb'); - assert.strictEqual(mockOpenTextDocument.firstArg.content, ''); - assert( - mockShowTextDocument.firstArg === 'untitled', - 'Expected it to call vscode to show the playground' + assert.strictEqual( + mockOpenTextDocument.firstCall.args[0].language, + 'mongodb' ); + assert.strictEqual(mockOpenTextDocument.firstCall.args[0].content, ''); + assert.strictEqual(mockShowTextDocument.firstCall.args[0], 'untitled'); }); test('mdb.runSelectedPlaygroundBlocks command should call runSelectedPlaygroundBlocks on the playground controller', async () => { - const mockRunSelectedPlaygroundBlocks: any = sinon.fake(); + const mockRunSelectedPlaygroundBlocks = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._playgroundController, 'runSelectedPlaygroundBlocks', @@ -1464,13 +1497,13 @@ suite('MDBExtensionController Test Suite', function () { await vscode.commands.executeCommand('mdb.runSelectedPlaygroundBlocks'); assert( - mockRunSelectedPlaygroundBlocks.called, + mockRunSelectedPlaygroundBlocks.calledOnce, 'Expected "runSelectedPlaygroundBlocks" to be called on the playground controller.' ); }); test('mdb.runAllPlaygroundBlocks command should call runAllPlaygroundBlocks on the playground controller', async () => { - const mockRunAllPlaygroundBlocks: any = sinon.fake(); + const mockRunAllPlaygroundBlocks = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._playgroundController, 'runAllPlaygroundBlocks', @@ -1479,13 +1512,13 @@ suite('MDBExtensionController Test Suite', function () { await vscode.commands.executeCommand('mdb.runAllPlaygroundBlocks'); assert( - mockRunAllPlaygroundBlocks.called, + mockRunAllPlaygroundBlocks.calledOnce, 'Expected "runAllPlaygroundBlocks" to be called on the playground controller.' ); }); test('mdb.changeActiveConnection command should call changeActiveConnection on the playground controller', async () => { - const mockChangeActiveConnection: any = sinon.fake(); + const mockChangeActiveConnection = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._connectionController, 'changeActiveConnection', @@ -1494,13 +1527,13 @@ suite('MDBExtensionController Test Suite', function () { await vscode.commands.executeCommand('mdb.changeActiveConnection'); assert( - mockChangeActiveConnection.called, + mockChangeActiveConnection.calledOnce, 'Expected "changeActiveConnection" to be called on the playground controller.' ); }); test('mdb.refreshPlaygrounds command should call refreshPlaygrounds on the playgrounds explorer controller', async () => { - const mockRefreshPlaygrounds: any = sinon.fake(); + const mockRefreshPlaygrounds = sinon.fake(); sinon.replace( mdbTestExtension.testExtensionController._playgroundsExplorer, 'refresh', @@ -1509,7 +1542,7 @@ suite('MDBExtensionController Test Suite', function () { await vscode.commands.executeCommand('mdb.refreshPlaygrounds'); assert( - mockRefreshPlaygrounds.called, + mockRefreshPlaygrounds.calledOnce, 'Expected "refreshPlaygrounds" to be called on the playground controller.' ); }); @@ -1544,7 +1577,7 @@ suite('MDBExtensionController Test Suite', function () { () => Promise.resolve() ); - const mockCopyToClipboard: any = sinon.fake(); + const mockCopyToClipboard = sinon.fake(); sinon.replaceGetter(vscode.env, 'clipboard', () => ({ writeText: mockCopyToClipboard, readText: sinon.fake() as any, @@ -1556,7 +1589,7 @@ suite('MDBExtensionController Test Suite', function () { ); assert.strictEqual(mockCopyToClipboard.called, true); assert.strictEqual( - mockCopyToClipboard.firstArg, + mockCopyToClipboard.firstCall.args[0], `{ "_id": "pancakes", "time": { @@ -1567,6 +1600,66 @@ suite('MDBExtensionController Test Suite', function () { assert.strictEqual(namespaceUsed, 'waffle.house'); }); + test("mdb.cloneDocumentFromTreeView event should open a playground with a document's content", async () => { + const mockDocument = { + _id: 'pancakes', + time: new Date('3001-01-01T05:00:00.000Z'), + objectIdField: new ObjectId('57e193d7a9cc81b4027498b2'), + }; + + let namespaceUsed = ''; + + const mockDataService: DataService = { + find: ( + namespace: string, + filter: object, + options: object, + callback: (error: Error | undefined, documents: object[]) => void + ) => { + namespaceUsed = namespace; + callback(undefined, [mockDocument]); + }, + } as any; + + const documentTreeItem = new DocumentTreeItem( + mockDocument, + 'waffle.house', + 0, + mockDataService, + () => Promise.resolve() + ); + + const mockCreatePlaygroundForCloneDocument = sinon.fake(); + sinon.replace( + mdbTestExtension.testExtensionController._playgroundController, + 'createPlaygroundForCloneDocument', + mockCreatePlaygroundForCloneDocument + ); + + await vscode.commands.executeCommand( + 'mdb.cloneDocumentFromTreeView', + documentTreeItem + ); + assert.strictEqual(mockCreatePlaygroundForCloneDocument.calledOnce, true); + assert.strictEqual( + mockCreatePlaygroundForCloneDocument.firstCall.args[0], + `{ + _id: 'pancakes', + time: ISODate('3001-01-01T05:00:00.000Z'), + objectIdField: ObjectId('57e193d7a9cc81b4027498b2') +}` + ); + assert.strictEqual( + mockCreatePlaygroundForCloneDocument.firstCall.args[1], + 'waffle' + ); + assert.strictEqual( + mockCreatePlaygroundForCloneDocument.firstCall.args[2], + 'house' + ); + assert.strictEqual(namespaceUsed, 'waffle.house'); + }); + test('mdb.deleteDocumentFromTreeView should not delete a document when the confirmation is cancelled', async () => { const mockDocument = { _id: 'pancakes', diff --git a/src/typings.d.ts b/src/typings.d.ts new file mode 100644 index 000000000..d3e0356a8 --- /dev/null +++ b/src/typings.d.ts @@ -0,0 +1,3 @@ +declare module 'mongodb-query-parser' { + const toJSString: (object: any, indentation?: number) => string; +}