From fdfa245fd044b587d6db2bb39e8ccbf2cafc2fe8 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 13:18:41 +0200 Subject: [PATCH 01/11] test: fake vscode as an external editor MONGOSH-1011 --- packages/cli-repl/test/e2e-editor.spec.ts | 174 ++++++++++++++-------- packages/cli-repl/test/repl-helpers.ts | 19 ++- 2 files changed, 122 insertions(+), 71 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index 2f72b248b0..14c331e4a6 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -1,15 +1,17 @@ import { expect } from 'chai'; +import fs, { promises as fsPromises } from 'fs'; import path from 'path'; -import { promises as fs } from 'fs'; import { promisify } from 'util'; import rimraf from 'rimraf'; import { eventually } from '../../../testing/eventually'; -import { useTmpdir, fakeExternalEditor, setTemporaryHomeDirectory } from './repl-helpers'; import { TestShell } from './test-shell'; +import { useTmpdir, fakeExternalEditor, setTemporaryHomeDirectory } from './repl-helpers'; describe('external editor e2e', () => { const tmpdir = useTmpdir(); + let tmpfilesWatcher: fs.FSWatcher; + let tmpEditorFile: string; let homedir: string; let env: Record; let shell: TestShell; @@ -30,8 +32,15 @@ describe('external editor e2e', () => { shell.assertNoErrors(); // make nyc happy when spawning npm below - await fs.mkdir(path.join(tmpdir.path, '.mongodb', '.nyc_output', 'processinfo'), { recursive: true }); - await fs.mkdir(path.join(tmpdir.path, 'mongodb', '.nyc_output', 'processinfo'), { recursive: true }); + await fsPromises.mkdir(path.join(tmpdir.path, '.mongodb', '.nyc_output', 'processinfo'), { recursive: true }); + await fsPromises.mkdir(path.join(tmpdir.path, 'mongodb', '.nyc_output', 'processinfo'), { recursive: true }); + + // make and watch dir for editor temp files + const editorPath = path.join(homedir, '.mongodb', 'mongosh', 'editor'); + await fsPromises.mkdir(editorPath, { recursive: true }); + tmpfilesWatcher = fs.watch(editorPath, (eventType, filename) => { + tmpEditorFile = filename; + }); }); afterEach(async() => { @@ -43,78 +52,117 @@ describe('external editor e2e', () => { // If it does, just log the error instead of failing all tests. console.error('Could not remove fake home directory:', err); } + tmpfilesWatcher.close(); }); - it('returns a modified identifier for fn', async() => { - const shellOriginalInput = "const fn = function () { console.log(111); }; edit('fn')"; - const editorOutput = `function () { - console.log(222); - };`; - const shellModifiedInput = 'fn = function () { console.log(222); };'; - const editor = await fakeExternalEditor(editorOutput); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); - - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsOutput(shellModifiedInput); + context('when not vscode editor', () => { + it('creates a file with .js extension', async() => { + const shellOriginalInput = 'edit 111'; + const editorOutput = 'edit 222'; + const shellModifiedInput = 'edit 222'; + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + expect(path.extname(tmpEditorFile)).to.equal('.js'); }); - }); - it('returns a modified identifier for var', async() => { - const shellOriginalInput = "const myVar = '111'; edit('myVar')"; - const editorOutput = "const myVar = '222';"; - const shellModifiedInput = "myVar = '222';"; - const editor = await fakeExternalEditor(editorOutput); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); - - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsOutput(shellModifiedInput); + it('returns a modified identifier for fn', async() => { + const shellOriginalInput = "const fn = function () { console.log(111); }; edit('fn')"; + const editorOutput = `function () { + console.log(222); + };`; + const shellModifiedInput = 'fn = function () { console.log(222); };'; + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); }); - }); - it('returns a modified identifier for a.b.c', async() => { - const shellOriginalInput = "const myObj = { field: { child: 'string value' } }; edit('myObj')"; - const editorOutput = `const myObj = { - field: { - child: 'new value' - } - };`; - const shellModifiedInput = "myObj = { field: { child: 'new value' } };"; - const editor = await fakeExternalEditor(editorOutput); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); - - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsOutput(shellModifiedInput); + it('returns a modified identifier for var', async() => { + const shellOriginalInput = "const myVar = '111'; edit('myVar')"; + const editorOutput = "const myVar = '222';"; + const shellModifiedInput = "myVar = '222';"; + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); }); - }); - it('returns an error when editor exits with exitCode 1', async() => { - const shellOriginalInput = 'edit function() {}'; - const editor = await fakeExternalEditor(); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); - await shell.executeLine('config.set("showStackTraces", true); print(process.env.PATH);'); + it('returns a modified identifier for a.b.c', async() => { + const shellOriginalInput = "const myObj = { field: { child: 'string value' } }; edit('myObj')"; + const editorOutput = `const myObj = { + field: { + child: 'new value' + } + };`; + const shellModifiedInput = "myObj = { field: { child: 'new value' } };"; + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + }); + + it('returns an error when editor exits with exitCode 1', async() => { + const shellOriginalInput = 'edit function() {}'; + const editor = await fakeExternalEditor({ tmpdir: tmpdir.path, name: 'editor' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsError('failed with an exit code 1'); + }); + }); + + it('opens an empty editor', async() => { + const output = ''; + const editor = await fakeExternalEditor({ output, tmpdir: tmpdir.path, name: 'editor' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsError('failed with an exit code 1'); + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine('edit'); + await eventually(() => { + shell.assertContainsOutput(output); + }); }); }); - it('opens an empty editor', async() => { - const output = ''; - const editor = await fakeExternalEditor(output); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + context('when vscode editor', () => { + beforeEach(async() => { + // make a fake dir for vscode mongodb extension + await fsPromises.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); + }); - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine('edit'); - await eventually(() => { - shell.assertContainsOutput(output); + it('creates a file with .mongodb extension', async() => { + const shellOriginalInput = 'edit 111'; + const editorOutput = 'edit 222'; + const shellModifiedInput = 'edit 222'; + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'code' }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + expect(path.extname(tmpEditorFile)).to.equal('.mongodb'); }); }); }); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 52c361899e..1bae479e06 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -77,29 +77,32 @@ async function readReplLogfile(logPath: string) { } -const fakeExternalEditor = async(output?: string) => { - const base = path.resolve(__dirname, '..', '..', '..', 'tmp', 'test', `${Date.now()}`, `${Math.random()}`); - const tmpDoc = path.join(base, 'editor-script.js'); +const fakeExternalEditor = async( + { output, tmpdir, name }: { output?: string, tmpdir: string, name: string } +) => { + const tmpDoc = path.join(tmpdir, name); let script: string; if (typeof output === 'string') { - script = `(async () => { + script = `#!/usr/bin/env node + (async () => { const tmpDoc = process.argv[process.argv.length - 1]; const { promises: { writeFile } } = require("fs"); await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 }); })()`; } else { - script = 'process.exit(1);'; + script = `#!/usr/bin/env node + process.exit(1);`; } await fs.mkdir(path.dirname(tmpDoc), { recursive: true, mode: 0o700 }); - await fs.writeFile(tmpDoc, script, { mode: 0o600 }); + await fs.writeFile(tmpDoc, script, { mode: 0o700 }); - return `node ${tmpDoc}`; + return tmpDoc; }; const setTemporaryHomeDirectory = () => { - const homedir: string = path.resolve(__dirname, '..', '..', '..', 'tmp', `cli-repl-home-${Date.now()}-${Math.random()}`); + const homedir: string = path.resolve(__dirname, '..', '..', '..', 'tmp', 'test', `cli-repl-home-${Date.now()}-${Math.random()}`); const env: Record = { ...process.env, HOME: homedir, USERPROFILE: homedir }; if (process.platform === 'win32') { From 6ddec436d98b2e5496bb34982b16fa215fe4fb09 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 15:04:42 +0200 Subject: [PATCH 02/11] test: assert extension inside script --- packages/cli-repl/test/e2e-editor.spec.ts | 47 +++++++++++------------ packages/cli-repl/test/repl-helpers.ts | 14 +++++-- packages/editor/src/editor.ts | 2 +- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index 14c331e4a6..f6a51e3171 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import fs, { promises as fsPromises } from 'fs'; import path from 'path'; +import { promises as fs } from 'fs'; import { promisify } from 'util'; import rimraf from 'rimraf'; @@ -10,8 +10,6 @@ import { useTmpdir, fakeExternalEditor, setTemporaryHomeDirectory } from './repl describe('external editor e2e', () => { const tmpdir = useTmpdir(); - let tmpfilesWatcher: fs.FSWatcher; - let tmpEditorFile: string; let homedir: string; let env: Record; let shell: TestShell; @@ -32,15 +30,8 @@ describe('external editor e2e', () => { shell.assertNoErrors(); // make nyc happy when spawning npm below - await fsPromises.mkdir(path.join(tmpdir.path, '.mongodb', '.nyc_output', 'processinfo'), { recursive: true }); - await fsPromises.mkdir(path.join(tmpdir.path, 'mongodb', '.nyc_output', 'processinfo'), { recursive: true }); - - // make and watch dir for editor temp files - const editorPath = path.join(homedir, '.mongodb', 'mongosh', 'editor'); - await fsPromises.mkdir(editorPath, { recursive: true }); - tmpfilesWatcher = fs.watch(editorPath, (eventType, filename) => { - tmpEditorFile = filename; - }); + await fs.mkdir(path.join(tmpdir.path, '.mongodb', '.nyc_output', 'processinfo'), { recursive: true }); + await fs.mkdir(path.join(tmpdir.path, 'mongodb', '.nyc_output', 'processinfo'), { recursive: true }); }); afterEach(async() => { @@ -52,7 +43,6 @@ describe('external editor e2e', () => { // If it does, just log the error instead of failing all tests. console.error('Could not remove fake home directory:', err); } - tmpfilesWatcher.close(); }); context('when not vscode editor', () => { @@ -60,7 +50,7 @@ describe('external editor e2e', () => { const shellOriginalInput = 'edit 111'; const editorOutput = 'edit 222'; const shellModifiedInput = 'edit 222'; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -68,7 +58,7 @@ describe('external editor e2e', () => { await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - expect(path.extname(tmpEditorFile)).to.equal('.js'); + shell.assertNoErrors(); }); it('returns a modified identifier for fn', async() => { @@ -77,7 +67,11 @@ describe('external editor e2e', () => { console.log(222); };`; const shellModifiedInput = 'fn = function () { console.log(222); };'; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.js', + tmpdir: tmpdir.path + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -91,7 +85,7 @@ describe('external editor e2e', () => { const shellOriginalInput = "const myVar = '111'; edit('myVar')"; const editorOutput = "const myVar = '222';"; const shellModifiedInput = "myVar = '222';"; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -109,7 +103,7 @@ describe('external editor e2e', () => { } };`; const shellModifiedInput = "myObj = { field: { child: 'new value' } };"; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'editor' }); + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -121,7 +115,7 @@ describe('external editor e2e', () => { it('returns an error when editor exits with exitCode 1', async() => { const shellOriginalInput = 'edit function() {}'; - const editor = await fakeExternalEditor({ tmpdir: tmpdir.path, name: 'editor' }); + const editor = await fakeExternalEditor({ tmpdir: tmpdir.path }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -133,7 +127,7 @@ describe('external editor e2e', () => { it('opens an empty editor', async() => { const output = ''; - const editor = await fakeExternalEditor({ output, tmpdir: tmpdir.path, name: 'editor' }); + const editor = await fakeExternalEditor({ output, tmpdir: tmpdir.path }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -147,22 +141,27 @@ describe('external editor e2e', () => { context('when vscode editor', () => { beforeEach(async() => { // make a fake dir for vscode mongodb extension - await fsPromises.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); + await fs.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); }); it('creates a file with .mongodb extension', async() => { const shellOriginalInput = 'edit 111'; const editorOutput = 'edit 222'; const shellModifiedInput = 'edit 222'; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, name: 'code' }); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.mongodb', + tmpdir: tmpdir.path + }); + const editorWithFlag = JSON.stringify(`${editor} --wait`); + const result = await shell.executeLine(`config.set("editor", ${editorWithFlag});`); expect(result).to.include('"editor" has been changed'); shell.writeInputLine(shellOriginalInput); await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - expect(path.extname(tmpEditorFile)).to.equal('.mongodb'); + shell.assertNoErrors(); }); }); }); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 1bae479e06..d508070534 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -78,9 +78,10 @@ async function readReplLogfile(logPath: string) { const fakeExternalEditor = async( - { output, tmpdir, name }: { output?: string, tmpdir: string, name: string } + { output, expectedExtension, tmpdir }: { output?: string, expectedExtension?: string, tmpdir: string } ) => { - const tmpDoc = path.join(tmpdir, name); + const editor = (expectedExtension === '.mongodb') ? 'code' : 'editor'; + const tmpDoc = path.join(tmpdir, editor); let script: string; if (typeof output === 'string') { @@ -88,8 +89,15 @@ const fakeExternalEditor = async( (async () => { const tmpDoc = process.argv[process.argv.length - 1]; const { promises: { writeFile } } = require("fs"); + const assert = require("assert"); + const path = require("path"); + await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 }); - })()`; + + if (${JSON.stringify(expectedExtension ?? '')}) { + assert.strictEqual(path.extname(tmpDoc), ${JSON.stringify(expectedExtension)}); + } + })().catch((err) => { process.nextTick(() => { throw err; }); });`; } else { script = `#!/usr/bin/env node process.exit(1);`; diff --git a/packages/editor/src/editor.ts b/packages/editor/src/editor.ts index 26872fe35c..a1ef913339 100644 --- a/packages/editor/src/editor.ts +++ b/packages/editor/src/editor.ts @@ -122,7 +122,7 @@ export class Editor { } _isVscodeApp(cmd: string): boolean { - const regex = /^(.*)[\/\\]?[cC]ode(.exe)?$/; + const regex = /^(.*)[\/\\]?[cC]ode(.exe)?((\s(.*))?)$/; return regex.test(cmd); } From 5ef48084d5721ae26181974bfa7ecceec698fcf3 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 15:11:28 +0200 Subject: [PATCH 03/11] test: move expectedExtension to a proper test --- packages/cli-repl/test/e2e-editor.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index f6a51e3171..5926621099 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -50,7 +50,11 @@ describe('external editor e2e', () => { const shellOriginalInput = 'edit 111'; const editorOutput = 'edit 222'; const shellModifiedInput = 'edit 222'; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.js', + tmpdir: tmpdir.path + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -67,11 +71,7 @@ describe('external editor e2e', () => { console.log(222); };`; const shellModifiedInput = 'fn = function () { console.log(222); };'; - const editor = await fakeExternalEditor({ - output: editorOutput, - expectedExtension: '.js', - tmpdir: tmpdir.path - }); + const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); From fd02e9748778d799e0779d821327aceaafae591f Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 16:43:34 +0200 Subject: [PATCH 04/11] test: using flags along with file names --- packages/cli-repl/test/e2e-editor.spec.ts | 117 +++++++++++++++++----- packages/cli-repl/test/repl-helpers.ts | 24 +++-- packages/editor/src/editor.ts | 2 +- 3 files changed, 110 insertions(+), 33 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index 5926621099..db6eecf298 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -49,11 +49,12 @@ describe('external editor e2e', () => { it('creates a file with .js extension', async() => { const shellOriginalInput = 'edit 111'; const editorOutput = 'edit 222'; - const shellModifiedInput = 'edit 222'; + const shellModifiedInput = '222'; const editor = await fakeExternalEditor({ output: editorOutput, expectedExtension: '.js', - tmpdir: tmpdir.path + tmpdir: tmpdir.path, + name: 'editor' }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -71,7 +72,11 @@ describe('external editor e2e', () => { console.log(222); };`; const shellModifiedInput = 'fn = function () { console.log(222); };'; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); + const editor = await fakeExternalEditor({ + output: editorOutput, + tmpdir: tmpdir.path, + name: 'editor' + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -85,7 +90,11 @@ describe('external editor e2e', () => { const shellOriginalInput = "const myVar = '111'; edit('myVar')"; const editorOutput = "const myVar = '222';"; const shellModifiedInput = "myVar = '222';"; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); + const editor = await fakeExternalEditor({ + output: editorOutput, + tmpdir: tmpdir.path, + name: 'editor' + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -103,7 +112,11 @@ describe('external editor e2e', () => { } };`; const shellModifiedInput = "myObj = { field: { child: 'new value' } };"; - const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path }); + const editor = await fakeExternalEditor({ + output: editorOutput, + tmpdir: tmpdir.path, + name: 'editor' + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -115,7 +128,10 @@ describe('external editor e2e', () => { it('returns an error when editor exits with exitCode 1', async() => { const shellOriginalInput = 'edit function() {}'; - const editor = await fakeExternalEditor({ tmpdir: tmpdir.path }); + const editor = await fakeExternalEditor({ + tmpdir: tmpdir.path, + name: 'editor' + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -127,7 +143,11 @@ describe('external editor e2e', () => { it('opens an empty editor', async() => { const output = ''; - const editor = await fakeExternalEditor({ output, tmpdir: tmpdir.path }); + const editor = await fakeExternalEditor({ + output, + tmpdir: tmpdir.path, + name: 'editor' + }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); @@ -139,29 +159,74 @@ describe('external editor e2e', () => { }); context('when vscode editor', () => { - beforeEach(async() => { - // make a fake dir for vscode mongodb extension - await fs.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); - }); + context('when mongodb extension is installed', () => { + beforeEach(async() => { + // make a fake dir for vscode mongodb extension + await fs.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); + }); - it('creates a file with .mongodb extension', async() => { - const shellOriginalInput = 'edit 111'; - const editorOutput = 'edit 222'; - const shellModifiedInput = 'edit 222'; - const editor = await fakeExternalEditor({ - output: editorOutput, - expectedExtension: '.mongodb', - tmpdir: tmpdir.path + it('creates a file with .mongodb extension', async() => { + const shellOriginalInput = 'edit 111'; + const editorOutput = 'edit 222'; + const shellModifiedInput = '222'; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.mongodb', + tmpdir: tmpdir.path, + name: 'code' + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + shell.assertNoErrors(); }); - const editorWithFlag = JSON.stringify(`${editor} --wait`); - const result = await shell.executeLine(`config.set("editor", ${editorWithFlag});`); - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsOutput(shellModifiedInput); + it('allows using flags along with file names', async() => { + const shellOriginalInput = "edit 'string with whitespaces'"; + const editorOutput = "'string with whitespaces and const x = 0;'"; + const shellModifiedInput = "'string with whitespaces and const x = 0;'"; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.mongodb', + tmpdir: tmpdir.path, + name: 'code', + flags: '--wait' + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + shell.assertNoErrors(); + }); + }); + + context('when mongodb extension is not installed', () => { + it('creates a file with .js extension', async() => { + const shellOriginalInput = "edit const x = 'y';"; + const editorOutput = "const x = 'zyz';"; + const shellModifiedInput = "const x = 'zyz';"; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.js', + tmpdir: tmpdir.path, + name: 'code' + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + shell.assertNoErrors(); }); - shell.assertNoErrors(); }); }); }); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index d508070534..d057205004 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -78,10 +78,15 @@ async function readReplLogfile(logPath: string) { const fakeExternalEditor = async( - { output, expectedExtension, tmpdir }: { output?: string, expectedExtension?: string, tmpdir: string } + { output, expectedExtension, tmpdir, name, flags }: { + output?: string, + expectedExtension?: string, + tmpdir: string, + name: string, + flags?: string + } ) => { - const editor = (expectedExtension === '.mongodb') ? 'code' : 'editor'; - const tmpDoc = path.join(tmpdir, editor); + const tmpDoc = path.join(tmpdir, name); let script: string; if (typeof output === 'string') { @@ -92,11 +97,18 @@ const fakeExternalEditor = async( const assert = require("assert"); const path = require("path"); - await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 }); - if (${JSON.stringify(expectedExtension ?? '')}) { assert.strictEqual(path.extname(tmpDoc), ${JSON.stringify(expectedExtension)}); } + + if (${JSON.stringify(flags ?? '')}) { + assert.strictEqual( + (${JSON.stringify(flags ?? '')}).split(/\s+/).every((val) => process.argv.includes(val)), + true + ); + } + + await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 }); })().catch((err) => { process.nextTick(() => { throw err; }); });`; } else { script = `#!/usr/bin/env node @@ -106,7 +118,7 @@ const fakeExternalEditor = async( await fs.mkdir(path.dirname(tmpDoc), { recursive: true, mode: 0o700 }); await fs.writeFile(tmpDoc, script, { mode: 0o700 }); - return tmpDoc; + return flags ? `${tmpDoc} ${flags}` : tmpDoc; }; const setTemporaryHomeDirectory = () => { diff --git a/packages/editor/src/editor.ts b/packages/editor/src/editor.ts index a1ef913339..56a6f12150 100644 --- a/packages/editor/src/editor.ts +++ b/packages/editor/src/editor.ts @@ -122,7 +122,7 @@ export class Editor { } _isVscodeApp(cmd: string): boolean { - const regex = /^(.*)[\/\\]?[cC]ode(.exe)?((\s(.*))?)$/; + const regex = /^(.*)[\/\\]?[cC]ode(.exe)?(\s(.*))?$/; return regex.test(cmd); } From 577910f5d090fa0b8b185ef9cfca766c8b70d178 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 16:53:11 +0200 Subject: [PATCH 05/11] refactor: stringify flags --- packages/cli-repl/test/repl-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index d057205004..624c56e3c7 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -103,7 +103,7 @@ const fakeExternalEditor = async( if (${JSON.stringify(flags ?? '')}) { assert.strictEqual( - (${JSON.stringify(flags ?? '')}).split(/\s+/).every((val) => process.argv.includes(val)), + (${JSON.stringify(flags)}).split(/\s+/).every((val) => process.argv.includes(val)), true ); } From 2df89be9146f3c5f4ae1b16cb70f513b48450073 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 17:09:36 +0200 Subject: [PATCH 06/11] refactor: use deepStrictEqual and slice --- packages/cli-repl/test/repl-helpers.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 624c56e3c7..3806178347 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -102,10 +102,7 @@ const fakeExternalEditor = async( } if (${JSON.stringify(flags ?? '')}) { - assert.strictEqual( - (${JSON.stringify(flags)}).split(/\s+/).every((val) => process.argv.includes(val)), - true - ); + assert.deepStrictEqual((${JSON.stringify(flags)}).split(/\s+/), process.argv.slice(2, -1)); } await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 }); From 187b7904b5714d44efa9b3b7797ebb4b1d0a78b8 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Mon, 4 Oct 2021 19:04:19 +0200 Subject: [PATCH 07/11] test: check if windows ignores shebang in script --- packages/cli-repl/test/e2e-editor.spec.ts | 148 ++++++++++++++-------- packages/cli-repl/test/repl-helpers.ts | 9 +- 2 files changed, 101 insertions(+), 56 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index db6eecf298..84a537f164 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -45,7 +45,7 @@ describe('external editor e2e', () => { } }); - context('when not vscode editor', () => { + context('when editor is node command', () => { it('creates a file with .js extension', async() => { const shellOriginalInput = 'edit 111'; const editorOutput = 'edit 222'; @@ -54,7 +54,8 @@ describe('external editor e2e', () => { output: editorOutput, expectedExtension: '.js', tmpdir: tmpdir.path, - name: 'editor' + name: 'editor', + isNodeCommand: true }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -75,7 +76,8 @@ describe('external editor e2e', () => { const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, - name: 'editor' + name: 'editor', + isNodeCommand: true }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -93,7 +95,8 @@ describe('external editor e2e', () => { const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, - name: 'editor' + name: 'editor', + isNodeCommand: true }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -115,7 +118,8 @@ describe('external editor e2e', () => { const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, - name: 'editor' + name: 'editor', + isNodeCommand: true }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -130,7 +134,8 @@ describe('external editor e2e', () => { const shellOriginalInput = 'edit function() {}'; const editor = await fakeExternalEditor({ tmpdir: tmpdir.path, - name: 'editor' + name: 'editor', + isNodeCommand: true }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -146,7 +151,8 @@ describe('external editor e2e', () => { const editor = await fakeExternalEditor({ output, tmpdir: tmpdir.path, - name: 'editor' + name: 'editor', + isNodeCommand: true }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); @@ -158,74 +164,110 @@ describe('external editor e2e', () => { }); }); - context('when vscode editor', () => { - context('when mongodb extension is installed', () => { - beforeEach(async() => { - // make a fake dir for vscode mongodb extension - await fs.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); - }); + context('when editor is path', () => { + before(function() { + if (process.platform === 'win32') { + return this.skip(); // Shebangs don't work on windows. + } + }); - it('creates a file with .mongodb extension', async() => { - const shellOriginalInput = 'edit 111'; - const editorOutput = 'edit 222'; - const shellModifiedInput = '222'; + context('not vscode', () => { + it('creates a file with .js extension', async() => { + const shellOriginalInput = 'edit name'; + const editorOutput = "const name = 'Succeeded!'"; + const shellModifiedInput = "const name = 'Succeeded!'"; const editor = await fakeExternalEditor({ output: editorOutput, - expectedExtension: '.mongodb', + expectedExtension: '.js', tmpdir: tmpdir.path, - name: 'code' + name: 'editor', + isNodeCommand: false, + flags: '--trace-uncaught' }); const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); expect(result).to.include('"editor" has been changed'); + shell.writeInputLine("const name = 'I want to test a sequence of writeInputLine'"); shell.writeInputLine(shellOriginalInput); await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); shell.assertNoErrors(); }); + }); - it('allows using flags along with file names', async() => { - const shellOriginalInput = "edit 'string with whitespaces'"; - const editorOutput = "'string with whitespaces and const x = 0;'"; - const shellModifiedInput = "'string with whitespaces and const x = 0;'"; - const editor = await fakeExternalEditor({ - output: editorOutput, - expectedExtension: '.mongodb', - tmpdir: tmpdir.path, - name: 'code', - flags: '--wait' + context('vscode', () => { + context('when mongodb extension is installed', () => { + beforeEach(async() => { + // make a fake dir for vscode mongodb extension + await fs.mkdir(path.join(homedir, '.vscode', 'extensions', 'mongodb.mongodb-vscode-0.0.0'), { recursive: true }); }); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsOutput(shellModifiedInput); + it('creates a file with .mongodb extension', async() => { + const shellOriginalInput = 'edit 111'; + const editorOutput = 'edit 222'; + const shellModifiedInput = '222'; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.mongodb', + tmpdir: tmpdir.path, + name: 'code', + isNodeCommand: false + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + shell.assertNoErrors(); }); - shell.assertNoErrors(); - }); - }); - context('when mongodb extension is not installed', () => { - it('creates a file with .js extension', async() => { - const shellOriginalInput = "edit const x = 'y';"; - const editorOutput = "const x = 'zyz';"; - const shellModifiedInput = "const x = 'zyz';"; - const editor = await fakeExternalEditor({ - output: editorOutput, - expectedExtension: '.js', - tmpdir: tmpdir.path, - name: 'code' + it('allows using flags along with file names', async() => { + const shellOriginalInput = "edit 'string with whitespaces'"; + const editorOutput = "'string with whitespaces and const x = 0;'"; + const shellModifiedInput = "'string with whitespaces and const x = 0;'"; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.mongodb', + tmpdir: tmpdir.path, + name: 'code', + flags: '--wait', + isNodeCommand: false + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + shell.assertNoErrors(); }); - const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + }); - expect(result).to.include('"editor" has been changed'); - shell.writeInputLine(shellOriginalInput); - await eventually(() => { - shell.assertContainsOutput(shellModifiedInput); + context('when mongodb extension is not installed', () => { + it('creates a file with .js extension', async() => { + const shellOriginalInput = "edit const x = 'y';"; + const editorOutput = "const x = 'zyz';"; + const shellModifiedInput = "const x = 'zyz';"; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.js', + tmpdir: tmpdir.path, + name: 'code', + isNodeCommand: false + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + shell.assertNoErrors(); }); - shell.assertNoErrors(); }); }); }); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 3806178347..1ae62e786a 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -78,12 +78,13 @@ async function readReplLogfile(logPath: string) { const fakeExternalEditor = async( - { output, expectedExtension, tmpdir, name, flags }: { + { output, expectedExtension, tmpdir, name, flags, isNodeCommand }: { output?: string, expectedExtension?: string, tmpdir: string, name: string, - flags?: string + flags?: string, + isNodeCommand: boolean } ) => { const tmpDoc = path.join(tmpdir, name); @@ -115,7 +116,9 @@ const fakeExternalEditor = async( await fs.mkdir(path.dirname(tmpDoc), { recursive: true, mode: 0o700 }); await fs.writeFile(tmpDoc, script, { mode: 0o700 }); - return flags ? `${tmpDoc} ${flags}` : tmpDoc; + const editor = isNodeCommand ? `node ${tmpDoc}` : tmpDoc; + + return flags ? `${editor} ${flags}` : editor; }; const setTemporaryHomeDirectory = () => { From 05534b89628a9a60165d75e893c01bc59f1bc2e6 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 5 Oct 2021 01:11:14 +0200 Subject: [PATCH 08/11] test: fix opening objects --- packages/cli-repl/test/e2e-editor.spec.ts | 24 +++++++------------ packages/cli-repl/test/repl-helpers.ts | 3 +-- packages/editor/src/editor.ts | 10 ++++++-- .../src/snippet-manager.spec.ts | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index 84a537f164..c99bc4d996 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -48,7 +48,7 @@ describe('external editor e2e', () => { context('when editor is node command', () => { it('creates a file with .js extension', async() => { const shellOriginalInput = 'edit 111'; - const editorOutput = 'edit 222'; + const editorOutput = '222'; const shellModifiedInput = '222'; const editor = await fakeExternalEditor({ output: editorOutput, @@ -64,7 +64,6 @@ describe('external editor e2e', () => { await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - shell.assertNoErrors(); }); it('returns a modified identifier for fn', async() => { @@ -90,8 +89,8 @@ describe('external editor e2e', () => { it('returns a modified identifier for var', async() => { const shellOriginalInput = "const myVar = '111'; edit('myVar')"; - const editorOutput = "const myVar = '222';"; - const shellModifiedInput = "myVar = '222';"; + const editorOutput = '"222"'; + const shellModifiedInput = 'myVar = "222"'; const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, @@ -109,12 +108,12 @@ describe('external editor e2e', () => { it('returns a modified identifier for a.b.c', async() => { const shellOriginalInput = "const myObj = { field: { child: 'string value' } }; edit('myObj')"; - const editorOutput = `const myObj = { - field: { - child: 'new value' + const editorOutput = `{ + "field": { + "child": "new value" } - };`; - const shellModifiedInput = "myObj = { field: { child: 'new value' } };"; + }`; + const shellModifiedInput = 'myObj = { "field": { "child": "new value" } }'; const editor = await fakeExternalEditor({ output: editorOutput, tmpdir: tmpdir.path, @@ -173,7 +172,6 @@ describe('external editor e2e', () => { context('not vscode', () => { it('creates a file with .js extension', async() => { - const shellOriginalInput = 'edit name'; const editorOutput = "const name = 'Succeeded!'"; const shellModifiedInput = "const name = 'Succeeded!'"; const editor = await fakeExternalEditor({ @@ -188,11 +186,10 @@ describe('external editor e2e', () => { expect(result).to.include('"editor" has been changed'); shell.writeInputLine("const name = 'I want to test a sequence of writeInputLine'"); - shell.writeInputLine(shellOriginalInput); + shell.writeInputLine('edit name'); await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - shell.assertNoErrors(); }); }); @@ -221,7 +218,6 @@ describe('external editor e2e', () => { await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - shell.assertNoErrors(); }); it('allows using flags along with file names', async() => { @@ -243,7 +239,6 @@ describe('external editor e2e', () => { await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - shell.assertNoErrors(); }); }); @@ -266,7 +261,6 @@ describe('external editor e2e', () => { await eventually(() => { shell.assertContainsOutput(shellModifiedInput); }); - shell.assertNoErrors(); }); }); }); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 1ae62e786a..6a3a7e260a 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -88,6 +88,7 @@ const fakeExternalEditor = async( } ) => { const tmpDoc = path.join(tmpdir, name); + const editor = isNodeCommand ? `node ${tmpDoc}` : tmpDoc; let script: string; if (typeof output === 'string') { @@ -116,8 +117,6 @@ const fakeExternalEditor = async( await fs.mkdir(path.dirname(tmpDoc), { recursive: true, mode: 0o700 }); await fs.writeFile(tmpDoc, script, { mode: 0o700 }); - const editor = isNodeCommand ? `node ${tmpDoc}` : tmpDoc; - return flags ? `${editor} ${flags}` : editor; }; diff --git a/packages/editor/src/editor.ts b/packages/editor/src/editor.ts index 56a6f12150..c36a7eeb37 100644 --- a/packages/editor/src/editor.ts +++ b/packages/editor/src/editor.ts @@ -152,8 +152,13 @@ export class Editor { } // If code is an identifier evaluate the string to see what the result is. - const evalResult = await this._loadExternalCode(code, '@(editor)'); - return evalResult.toString(); + const evalResult: any = await this._loadExternalCode(code, '@(editor)'); + + if (typeof evalResult === 'function') { + return evalResult.toString(); + } + + return JSON.stringify(evalResult); } _prepareResult({ originalCode, modifiedCode }: { @@ -204,6 +209,7 @@ export class Editor { if (exitCode === 0) { const modifiedCode = await this._readAndDeleteTempFile(tmpDoc); const result = this._prepareResult({ originalCode: code, modifiedCode }); + // Write a content from the editor to the parent readable stream. this._input.unshift(result); return; diff --git a/packages/snippet-manager/src/snippet-manager.spec.ts b/packages/snippet-manager/src/snippet-manager.spec.ts index 8bf8cb542a..450eb1c054 100644 --- a/packages/snippet-manager/src/snippet-manager.spec.ts +++ b/packages/snippet-manager/src/snippet-manager.spec.ts @@ -472,7 +472,7 @@ describe('SnippetManager', () => { exists = true; } catch { /* does not exist, all good */ } if (exists) { - await fs.rm(hiddenPkgLock); + await fs.unlink(hiddenPkgLock); } }); const result = await snippetManager.runSnippetCommand(['outdated']); From 3fcb8c8e5c54c0009ac53c159584151866453d73 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 5 Oct 2021 12:30:11 +0200 Subject: [PATCH 09/11] test: refactor unit tests --- packages/editor/src/editor.spec.ts | 405 +++++++++++++++++++---------- 1 file changed, 273 insertions(+), 132 deletions(-) diff --git a/packages/editor/src/editor.spec.ts b/packages/editor/src/editor.spec.ts index 22155505ad..a789a66368 100644 --- a/packages/editor/src/editor.spec.ts +++ b/packages/editor/src/editor.spec.ts @@ -36,41 +36,27 @@ describe('Editor', () => { let vscodeDir: string; let tmpDir: string; let contextObject: any; - let editor: Editor; - let makeEditor: () => Editor; let busMessages: ({ ev: any, data?: any })[]; let cmd: string | null; - let mockLoadExternalCodeResult: (...args: any[]) => { args: any[]; done: boolean }; + let makeEditor: any; beforeEach(async() => { input = new PassThrough(); base = path.resolve(__dirname, '..', '..', '..', 'tmp', 'test', `${Date.now()}`, `${new bson.ObjectId()}`); vscodeDir = path.join(base, '.vscode'); tmpDir = path.join(base, 'editor'); - cmd = null; - contextObject = { - config: { - get(key: string): any { - switch (key) { - case 'editor': - return cmd; - default: - throw new Error(`Don’t know what to do with config key ${key}`); - } - } - }, - print: sinon.stub() - }; - mockLoadExternalCodeResult = function wrapper(...args: any[]) { - return { args, done: true }; - }; - - delete process.env.EDITOR; const messageBus = new Nanobus('mongosh-editor-test'); busMessages = []; + messageBus.on('*', (ev: any, data: any) => { + if (typeof data === 'number') { + busMessages.push({ ev }); + } else { + busMessages.push({ ev, data }); + } + }); - makeEditor = () => new Editor({ + makeEditor = (fakeLoadExternalCodeResult: any) => new Editor({ input, vscodeDir, tmpDir, @@ -80,16 +66,7 @@ describe('Editor', () => { registerPlugin: sinon.stub(), messageBus } as any, - loadExternalCode: (): any => mockLoadExternalCodeResult - }); - - editor = makeEditor(); - messageBus.on('*', (ev: any, data: any) => { - if (typeof data === 'number') { - busMessages.push({ ev }); - } else { - busMessages.push({ ev, data }); - } + loadExternalCode: (): Promise => fakeLoadExternalCodeResult }); // make nyc happy when we spawn npm below @@ -100,138 +77,302 @@ describe('Editor', () => { await promisify(rimraf)(path.resolve(base, '..')); }); - it('_isVscodeApp returns true if command is code', () => { - const isVscodeApp = editor._isVscodeApp('code'); - expect(isVscodeApp).to.be.equal(true); - }); + describe('_getExtension', () => { - it('_isVscodeApp returns true if command is path to code', () => { - const isVscodeApp = editor._isVscodeApp('/usr/local/bin/code'); - expect(isVscodeApp).to.be.equal(true); }); - it('_isVscodeApp returns true if command is path to code on windows', () => { - const isVscodeApp = editor._isVscodeApp('C:\\Program Files\\Microsoft VS Code\\Code.exe'); - expect(isVscodeApp).to.be.equal(true); - }); + describe('_getEditor', () => { + let editor: Editor; + + beforeEach(() => { + cmd = null; + contextObject = { + config: { + get(key: string): any { + switch (key) { + case 'editor': + return cmd; + default: + throw new Error(`Don’t know what to do with config key ${key}`); + } + } + }, + print: sinon.stub() + }; + delete process.env.EDITOR; + editor = makeEditor(); + }); - it('_isVscodeApp returns false if command is nano', () => { - const isVscodeApp = editor._isVscodeApp('nano'); - expect(isVscodeApp).to.be.equal(false); - }); + it('returns an editor value from process.env.EDITOR', async() => { + process.env.EDITOR = 'neweprocessditor'; + const editorName = await editor._getEditor(); + expect(editorName).to.be.equal(process.env.EDITOR); + }); - it('_isIdentifier returns false if a command is an empty find statement', () => { - const isIdentifier = editor._isIdentifier('db.test.find()'); - expect(isIdentifier).to.be.equal(false); + it('returns an editor value from the mongosh config', async() => { + cmd = 'newecmdditor'; + process.env.EDITOR = 'neweprocessditor'; + const editorName = await editor._getEditor(); + expect(editorName).to.be.equal(cmd); + }); }); - it('_isIdentifier returns false if a command is a find statement with a query', () => { - const isIdentifier = editor._isIdentifier("db.test.find({ name: 'lena' })"); - expect(isIdentifier).to.be.equal(false); - }); + describe('_createTempFile', () => { - it('_isIdentifier returns true if a command is an identifier written with dots', () => { - const isIdentifier = editor._isIdentifier('db.test.find'); - expect(isIdentifier).to.be.equal(true); }); - it('_isIdentifier returns false if a command is an identifier written as an array and double quotes', () => { - const isIdentifier = editor._isIdentifier('db["test"]find'); - expect(isIdentifier).to.be.equal(false); - }); + describe('_readAndDeleteTempFile', () => { - it('_isIdentifier returns false if a command is an identifier written as an array and single quotes', () => { - const isIdentifier = editor._isIdentifier("db['test']find"); - expect(isIdentifier).to.be.equal(false); }); - it('_isIdentifier returns true if it contains $', () => { - const isIdentifier = editor._isIdentifier('$something'); - expect(isIdentifier).to.be.equal(true); - }); + describe('_isVscodeApp', () => { + let editor: Editor; + + beforeEach(() => { + contextObject = { + config: { + get(key: string): any { + switch (key) { + case 'editor': + return null; + default: + throw new Error(`Don’t know what to do with config key ${key}`); + } + } + }, + print: sinon.stub() + }; + editor = makeEditor(); + }); - it('_isIdentifier returns false for sum of numbers', () => { - const isIdentifier = editor._isIdentifier('1 + 2'); - expect(isIdentifier).to.be.equal(false); - }); + it('returns true if command is code', () => { + const isVscodeApp = editor._isVscodeApp('code'); + expect(isVscodeApp).to.be.equal(true); + }); - it('_isIdentifier returns false for a class', () => { - const isIdentifier = editor._isIdentifier('class A {}'); - expect(isIdentifier).to.be.equal(false); - }); + it('returns true if command is path to code', () => { + const isVscodeApp = editor._isVscodeApp('/usr/local/bin/code'); + expect(isVscodeApp).to.be.equal(true); + }); - it('_isIdentifier returns false for a string', () => { - const isIdentifier = editor._isIdentifier('"some string"'); - expect(isIdentifier).to.be.equal(false); - }); + it('returns true if command is path to code on windows', () => { + const isVscodeApp = editor._isVscodeApp('C:\\Program Files\\Microsoft VS Code\\Code.exe'); + expect(isVscodeApp).to.be.equal(true); + }); - it('_isIdentifier returns false for a number', () => { - const isIdentifier = editor._isIdentifier('111'); - expect(isIdentifier).to.be.equal(false); + it('returns false if command is nano', () => { + const isVscodeApp = editor._isVscodeApp('nano'); + expect(isVscodeApp).to.be.equal(false); + }); }); - it('_getEditor returns an editor value from process.env.EDITOR', async() => { - process.env.EDITOR = 'neweprocessditor'; - const editorName = await editor._getEditor(); - expect(editorName).to.be.equal(process.env.EDITOR); - }); + describe('_isIdentifier', () => { + let editor: Editor; + + beforeEach(() => { + contextObject = { + config: { + get(key: string): any { + switch (key) { + case 'editor': + return cmd; + default: + throw new Error(`Don’t know what to do with config key ${key}`); + } + } + }, + print: sinon.stub() + }; + editor = makeEditor(); + }); - it('_getEditor returns an editor value from the mongosh config', async() => { - cmd = 'newecmdditor'; - process.env.EDITOR = 'neweprocessditor'; - const editorName = await editor._getEditor(); - expect(editorName).to.be.equal(cmd); - }); + it('returns false if a command is an empty find statement', () => { + const isIdentifier = editor._isIdentifier('db.test.find()'); + expect(isIdentifier).to.be.equal(false); + }); - it('runEditCommand returns an error for not existing editor', async() => { - try { - await editor.runEditCommand(''); - } catch (error) { - expect(error.message).to.include('Command failed with an error: please define an external editor'); - } - }); + it('returns false if a command is a find statement with a query', () => { + const isIdentifier = editor._isIdentifier("db.test.find({ name: 'lena' })"); + expect(isIdentifier).to.be.equal(false); + }); - it('_getEditorContent returns function implementation', async() => { - const code = 'db.test.find'; - const content = (await editor._getEditorContent(code)).replace(/\r\n/g, '\n'); - expect(content).to.be.equal('function wrapper(...args) {\n return { args, done: true };\n }'); - }); + it('returns true if a command is an identifier written with dots', () => { + const isIdentifier = editor._isIdentifier('db.test.find'); + expect(isIdentifier).to.be.equal(true); + }); - it('_getEditorContent returns function', async() => { - const code = "function foo() { return 'a b'; }"; - const content = await editor._getEditorContent(code); - expect(content).to.be.equal(code); - }); + it('returns false if a command is an identifier written as an array and double quotes', () => { + const isIdentifier = editor._isIdentifier('db["test"]find'); + expect(isIdentifier).to.be.equal(false); + }); - it('_getEditorContent returns an unmodified statment', async() => { - const code = 'db.coll.find()'; - const content = await editor._getEditorContent(code); - expect(content).to.be.equal(code); - }); + it('returns false if a command is an identifier written as an array and single quotes', () => { + const isIdentifier = editor._isIdentifier("db['test']find"); + expect(isIdentifier).to.be.equal(false); + }); + + it('returns true if it contains $', () => { + const isIdentifier = editor._isIdentifier('$something'); + expect(isIdentifier).to.be.equal(true); + }); + + it('returns false for sum of numbers', () => { + const isIdentifier = editor._isIdentifier('1 + 2'); + expect(isIdentifier).to.be.equal(false); + }); + + it('returns false for a class', () => { + const isIdentifier = editor._isIdentifier('class A {}'); + expect(isIdentifier).to.be.equal(false); + }); + + it('returns false for a string', () => { + const isIdentifier = editor._isIdentifier('"some string"'); + expect(isIdentifier).to.be.equal(false); + }); - it('_getEditorContent returns the last opened content for the empty edit command', async() => { - editor._lastContent = 'db.test.find()'; - const content = await editor._getEditorContent(''); - expect(content).to.be.equal('db.test.find()'); + it('returns false for a number', () => { + const isIdentifier = editor._isIdentifier('111'); + expect(isIdentifier).to.be.equal(false); + }); }); - it('_getEditorContent returns a new content for not empty edit command', async() => { - editor._lastContent = 'db.coll.find()'; - const content = await editor._getEditorContent('1 + 1'); - expect(content).to.be.equal('1 + 1'); + describe('_isNumeric', () => { + }); - it('_prepareResult returns an assignment statement for an identifier', () => { - const result = editor._prepareResult({ originalCode: 'fn', modifiedCode: 'function() { console.log(222); };' }); - expect(result).to.be.equal('fn = function() { console.log(222); };'); + describe('_getEditorContent', () => { + let editor: Editor; + + beforeEach(() => { + contextObject = { + config: { + get(key: string): any { + switch (key) { + case 'editor': + return null; + default: + throw new Error(`Don’t know what to do with config key ${key}`); + } + } + }, + print: sinon.stub() + }; + }); + + it('returns a function implementation', async() => { + const fakeLoadExternalCodeResult = function wrapper(...args: any[]) { + return { args, done: true }; + }; + editor = makeEditor(fakeLoadExternalCodeResult); + const code = 'db.test.find'; + const content = (await editor._getEditorContent(code)); + expect(content).to.be.equal('function wrapper(...args) {\n return { args, done: true };\n }'); + }); + + it('returns var', async() => { + editor = makeEditor(111); + const code = 'myVar'; + const content = (await editor._getEditorContent(code)); + expect(content).to.be.equal('111'); + }); + + it('returns a.b.c', async() => { + editor = makeEditor({ field: { child: 'string value' } }); + const code = 'myObj'; + const content = (await editor._getEditorContent(code)); + expect(content).to.be.equal('{"field":{"child":"string value"}}'); + }); + + it('returns function', async() => { + editor = makeEditor({ field: { child: 'string value' } }); + const code = "function foo() { return 'a b'; }"; + const content = await editor._getEditorContent(code); + expect(content).to.be.equal(code); + }); + + it('returns an unmodified statment', async() => { + editor = makeEditor(); + const code = 'db.coll.find()'; + const content = await editor._getEditorContent(code); + expect(content).to.be.equal(code); + }); + + it('returns a string', async() => { + editor = makeEditor(111); + const code = '"111"'; + const content = await editor._getEditorContent(code); + expect(content).to.be.equal(code); + }); + + it('returns the last opened content for the empty edit command', async() => { + editor = makeEditor(); + editor._lastContent = 'db.test.find()'; + const content = await editor._getEditorContent(''); + expect(content).to.be.equal('db.test.find()'); + }); + + it('returns a new content for not empty edit command', async() => { + editor = makeEditor(); + editor._lastContent = 'db.coll.find()'; + const content = await editor._getEditorContent('1 + 1'); + expect(content).to.be.equal('1 + 1'); + }); }); - it('_prepareResult returns an assignment statement for an identifier', () => { - const result = editor._prepareResult({ originalCode: '111', modifiedCode: '222' }); - expect(result).to.be.equal('222'); + describe('_prepareResult', () => { + let editor: Editor; + + beforeEach(() => { + contextObject = { + config: { + get(key: string): any { + switch (key) { + case 'editor': + return cmd; + default: + throw new Error(`Don’t know what to do with config key ${key}`); + } + } + }, + print: sinon.stub() + }; + editor = makeEditor(); + }); + + it('returns an assignment statement for an identifier', () => { + const result = editor._prepareResult({ originalCode: 'fn', modifiedCode: 'function() { console.log(222); };' }); + expect(result).to.be.equal('fn = function() { console.log(222); };'); + }); + + it('returns an assignment statement for an identifier', () => { + const result = editor._prepareResult({ originalCode: '111', modifiedCode: '222' }); + expect(result).to.be.equal('222'); + }); }); - context('runEditCommand', () => { + describe('runEditCommand', () => { + let editor: Editor; + + beforeEach(() => { + cmd = null; + contextObject = { + config: { + get(key: string): any { + switch (key) { + case 'editor': + return cmd; + default: + throw new Error(`Don’t know what to do with config key ${key}`); + } + } + }, + print: sinon.stub() + }; + delete process.env.EDITOR; + editor = makeEditor(); + }); + context('when editor is not defined', () => { it('returns please define an external editor error', async() => { try { From 8161d8554e07b2dbba961baec858ef0d00bf786b Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 5 Oct 2021 13:31:35 +0200 Subject: [PATCH 10/11] refactor: change test name and check one windows test --- packages/cli-repl/test/e2e-editor.spec.ts | 2 +- packages/editor/src/editor.spec.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index c99bc4d996..90125e6ac0 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -163,7 +163,7 @@ describe('external editor e2e', () => { }); }); - context('when editor is path', () => { + context('when editor is executable file', () => { before(function() { if (process.platform === 'win32') { return this.skip(); // Shebangs don't work on windows. diff --git a/packages/editor/src/editor.spec.ts b/packages/editor/src/editor.spec.ts index a789a66368..eb8736a308 100644 --- a/packages/editor/src/editor.spec.ts +++ b/packages/editor/src/editor.spec.ts @@ -261,13 +261,11 @@ describe('Editor', () => { }); it('returns a function implementation', async() => { - const fakeLoadExternalCodeResult = function wrapper(...args: any[]) { - return { args, done: true }; - }; + const fakeLoadExternalCodeResult = function wrapper(...args: any[]) { return { args, done: true }; }; editor = makeEditor(fakeLoadExternalCodeResult); const code = 'db.test.find'; const content = (await editor._getEditorContent(code)); - expect(content).to.be.equal('function wrapper(...args) {\n return { args, done: true };\n }'); + expect(content).to.be.equal('function wrapper(...args) { return { args, done: true }; }'); }); it('returns var', async() => { From 0320432946054cdc1c4074fa4feb00905ce9fbc9 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 5 Oct 2021 13:44:55 +0200 Subject: [PATCH 11/11] test: return fix for multiline on windows --- packages/editor/src/editor.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/editor.spec.ts b/packages/editor/src/editor.spec.ts index eb8736a308..894d0cb103 100644 --- a/packages/editor/src/editor.spec.ts +++ b/packages/editor/src/editor.spec.ts @@ -261,11 +261,13 @@ describe('Editor', () => { }); it('returns a function implementation', async() => { - const fakeLoadExternalCodeResult = function wrapper(...args: any[]) { return { args, done: true }; }; + const fakeLoadExternalCodeResult = function wrapper(...args: any[]) { + return { args, done: true }; + }; editor = makeEditor(fakeLoadExternalCodeResult); const code = 'db.test.find'; - const content = (await editor._getEditorContent(code)); - expect(content).to.be.equal('function wrapper(...args) { return { args, done: true }; }'); + const content = (await editor._getEditorContent(code)).replace(/\r\n/g, '\n'); + expect(content).to.be.equal('function wrapper(...args) {\n return { args, done: true };\n }'); }); it('returns var', async() => {