diff --git a/packages/cli-repl/test/e2e-editor.spec.ts b/packages/cli-repl/test/e2e-editor.spec.ts index 2f72b248b0..90125e6ac0 100644 --- a/packages/cli-repl/test/e2e-editor.spec.ts +++ b/packages/cli-repl/test/e2e-editor.spec.ts @@ -5,8 +5,8 @@ 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(); @@ -45,76 +45,224 @@ describe('external editor e2e', () => { } }); - 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 editor is node command', () => { + it('creates a file with .js extension', async() => { + const shellOriginalInput = 'edit 111'; + const editorOutput = '222'; + const shellModifiedInput = '222'; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.js', + tmpdir: tmpdir.path, + name: 'editor', + isNodeCommand: 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('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', + isNodeCommand: 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('returns a modified identifier for var', async() => { + const shellOriginalInput = "const myVar = '111'; edit('myVar')"; + const editorOutput = '"222"'; + const shellModifiedInput = 'myVar = "222"'; + const editor = await fakeExternalEditor({ + output: editorOutput, + tmpdir: tmpdir.path, + name: 'editor', + isNodeCommand: 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('returns a modified identifier for a.b.c', async() => { + const shellOriginalInput = "const myObj = { field: { child: 'string value' } }; edit('myObj')"; + const editorOutput = `{ + "field": { + "child": "new value" + } + }`; + const shellModifiedInput = 'myObj = { "field": { "child": "new value" } }'; + const editor = await fakeExternalEditor({ + output: editorOutput, + tmpdir: tmpdir.path, + name: 'editor', + isNodeCommand: 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('returns an error when editor exits with exitCode 1', async() => { + const shellOriginalInput = 'edit function() {}'; + const editor = await fakeExternalEditor({ + tmpdir: tmpdir.path, + name: 'editor', + isNodeCommand: 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.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', + isNodeCommand: true + }); + const result = await shell.executeLine(`config.set("editor", ${JSON.stringify(editor)});`); + + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine('edit'); + await eventually(() => { + shell.assertContainsOutput(output); + }); }); }); - 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' + context('when editor is executable file', () => { + before(function() { + if (process.platform === 'win32') { + return this.skip(); // Shebangs don't work on windows. } - };`; - 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 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);'); + context('not vscode', () => { + it('creates a file with .js extension', async() => { + const editorOutput = "const name = 'Succeeded!'"; + const shellModifiedInput = "const name = 'Succeeded!'"; + const editor = await fakeExternalEditor({ + output: editorOutput, + expectedExtension: '.js', + tmpdir: tmpdir.path, + 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(shellOriginalInput); - await eventually(() => { - shell.assertContainsError('failed with an exit code 1'); + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine("const name = 'I want to test a sequence of writeInputLine'"); + shell.writeInputLine('edit name'); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + }); }); - }); - 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('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 }); + }); + + 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); + }); + }); + + 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); + }); + }); + }); + + 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('edit'); - await eventually(() => { - shell.assertContainsOutput(output); + expect(result).to.include('"editor" has been changed'); + shell.writeInputLine(shellOriginalInput); + await eventually(() => { + shell.assertContainsOutput(shellModifiedInput); + }); + }); + }); }); }); }); diff --git a/packages/cli-repl/test/repl-helpers.ts b/packages/cli-repl/test/repl-helpers.ts index 52c361899e..6a3a7e260a 100644 --- a/packages/cli-repl/test/repl-helpers.ts +++ b/packages/cli-repl/test/repl-helpers.ts @@ -77,29 +77,51 @@ 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, expectedExtension, tmpdir, name, flags, isNodeCommand }: { + output?: string, + expectedExtension?: string, + tmpdir: string, + name: string, + flags?: string, + isNodeCommand: boolean + } +) => { + const tmpDoc = path.join(tmpdir, name); + const editor = isNodeCommand ? `node ${tmpDoc}` : tmpDoc; 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"); + const assert = require("assert"); + const path = require("path"); + + if (${JSON.stringify(expectedExtension ?? '')}) { + assert.strictEqual(path.extname(tmpDoc), ${JSON.stringify(expectedExtension)}); + } + + if (${JSON.stringify(flags ?? '')}) { + assert.deepStrictEqual((${JSON.stringify(flags)}).split(/\s+/), process.argv.slice(2, -1)); + } + await writeFile(tmpDoc, ${JSON.stringify(output)}, { mode: 0o600 }); - })()`; + })().catch((err) => { process.nextTick(() => { throw err; }); });`; } 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 flags ? `${editor} ${flags}` : editor; }; 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') { diff --git a/packages/editor/src/editor.spec.ts b/packages/editor/src/editor.spec.ts index 22155505ad..894d0cb103 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)).replace(/\r\n/g, '\n'); + 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 { diff --git a/packages/editor/src/editor.ts b/packages/editor/src/editor.ts index 26872fe35c..c36a7eeb37 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); } @@ -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']);