UNPKG

unexpected-markdown

Version:

A markdown parser that evaluates examples and highligh syntax using unexpected

456 lines (418 loc) 17.2 kB
/* eslint-disable mocha/no-nested-tests, mocha/no-identical-title */ var Module = require('module'); var path = require('path'); var vm = require('vm'); var convertMarkdownToMocha = require('../lib/convertMarkdownToMocha'); var esprima = require('esprima'); var escodegen = require('escodegen'); var projectPath = require('path').resolve(__dirname, '..'); function codeToString(obj) { var ast; if (typeof obj === 'function') { obj = `(${obj.toString()}());`; } else { obj = `(function () {${obj}}());`; } ast = esprima.parse(obj).body[0].expression.callee.body; return escodegen.generate(ast); } function createRequire(filepath) { const filename = path.join(filepath, 'noop.js'); // eslint-disable-next-line node/no-deprecated-api return (Module.createRequire || Module.createRequireFromPath)(filename); } function createTestRunnerEnvironment(code) { const blocks = []; // collect blocks registered with before and it const context = { executeTestBlocks: async function () { // execute the collected blocks in order for (const block of blocks) { await block(); } }, expect: require('unexpected'), describe: (_, fn) => fn(), before: (block) => blocks.push(block), it: (_, block) => blocks.push(block), require: createRequire(path.resolve(__dirname, '..')), }; context.global = context; return { get code() { return `${code}\nexecuteTestBlocks()`; }, get context() { return context; }, }; } var expect = require('unexpected') .clone() .use(require('magicpen-prism')) .addAssertion( '<string> to come out as <function|string>', function (expect, subject, value) { expect( codeToString(convertMarkdownToMocha(subject).code).replace( / {4}var fileName = '<inline code>'[\s\S]*$/, '}' ), 'to equal', codeToString(value).replace(/<projectPath>/g, projectPath) ); } ); var synchronousSuccessfulSnippet = "var foo = 'abc';\n" + "expect(foo, 'to equal', 'abc');\n"; var returningSuccessfulSnippet = "var blah = 'abc';\n" + "if (blah === 'abc') {\n" + ' return expect.promise(function (resolve, reject) {\n' + ' setImmediate(resolve);\n' + ' });\n' + '} else {\n' + ' return 456;\n' + '}\n'; var synchronousThrowingSnippet = "var bar = 'abc';\n" + "expect(bar, 'to equal', 'def');\n"; function fences(code, language) { return `\`\`\`${language || 'js'}\n${code}\n\`\`\`\n`; } describe('convertMarkdownToMocha', function () { it('should convert a returning snippet expected to be successful', function () { expect(fences(returningSuccessfulSnippet), 'to come out as', function () { var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); var globalExpect = global.expect; describe('<inline code>', function () { var expect = globalExpect.clone(); expect.output.preferredWidth = 80; before(async function () { var md = await new Markdown( "```js\nvar blah = 'abc';\nif (blah === 'abc') {\n return expect.promise(function (resolve, reject) {\n setImmediate(resolve);\n });\n} else {\n return 456;\n}\n\n```\n", { marker: 'unexpected-markdown', capture: 'return', pwdPath: '<projectPath>', fileGlobals: { expect: () => expect.clone() }, } ); await md.evaluate(); this.evaluatedSnippets = md.getSnippets(); this.isNextEvaluatedSnippetOutput = (index) => { var nextSnippet = this.evaluatedSnippets.get(index + 1); return !!nextSnippet && nextSnippet.lang === 'output'; }; }); it('example #1 (<inline code>:2:1) should succeed', function () { var snippetIndex = 0; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail(`snippet evaluation caused an error\n\n${output.text}`); } }); }); }); }); it('should convert a returning snippet expected to fail', function () { expect( `${fences(returningSuccessfulSnippet)}\n${fences( 'theErrorMessage', 'output' )}`, 'to come out as', function () { var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); var globalExpect = global.expect; describe('<inline code>', function () { var expect = globalExpect.clone(); expect.output.preferredWidth = 80; before(async function () { var md = await new Markdown( "```js\nvar blah = 'abc';\nif (blah === 'abc') {\n return expect.promise(function (resolve, reject) {\n setImmediate(resolve);\n });\n} else {\n return 456;\n}\n\n```\n\n```output\ntheErrorMessage\n```\n", { marker: 'unexpected-markdown', capture: 'return', pwdPath: '<projectPath>', fileGlobals: { expect: () => expect.clone() }, } ); await md.evaluate(); this.evaluatedSnippets = md.getSnippets(); this.isNextEvaluatedSnippetOutput = (index) => { var nextSnippet = this.evaluatedSnippets.get(index + 1); return !!nextSnippet && nextSnippet.lang === 'output'; }; }); it('example #1 (<inline code>:2:1) should succeed', function () { var snippetIndex = 0; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail( `snippet evaluation caused an error\n\n${output.text}` ); } }); it('example #2 (<inline code>:14:1) should succeed with the correct output', function () { var snippetIndex = 1; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var writtenOutput = 'theErrorMessage'; expect(evaluatedSnippet.output.text, 'to equal', writtenOutput); }); }); } ); }); it('should convert a snippet where the output block has cleanStackTrace:true', function () { if (process.env.NODE_ENV === 'coverage') return this.skip(); expect( `${fences( returningSuccessfulSnippet )}\n<!-- unexpected-markdown cleanStackTrace:true -->\n${fences( 'theErrorMessage', 'output' )}`, 'to come out as', function () { var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); var globalExpect = global.expect; describe('<inline code>', function () { var expect = globalExpect.clone(); expect.output.preferredWidth = 80; before(async function () { var md = await new Markdown( "```js\nvar blah = 'abc';\nif (blah === 'abc') {\n return expect.promise(function (resolve, reject) {\n setImmediate(resolve);\n });\n} else {\n return 456;\n}\n\n```\n\n<!-- unexpected-markdown cleanStackTrace:true -->\n```output\ntheErrorMessage\n```\n", { marker: 'unexpected-markdown', capture: 'return', pwdPath: '<projectPath>', fileGlobals: { expect: () => expect.clone() }, } ); await md.evaluate(); this.evaluatedSnippets = md.getSnippets(); this.isNextEvaluatedSnippetOutput = (index) => { var nextSnippet = this.evaluatedSnippets.get(index + 1); return !!nextSnippet && nextSnippet.lang === 'output'; }; }); it('example #1 (<inline code>:2:1) should succeed', function () { var snippetIndex = 0; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail( `snippet evaluation caused an error\n\n${output.text}` ); } }); it('example #2 (<inline code>:14:1) should succeed with the correct output', function () { var snippetIndex = 1; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var writtenOutput = 'theErrorMessage'; expect(evaluatedSnippet.output.text, 'to equal', writtenOutput); }); }); } ); }); it('should convert a returning snippet expected to fail followed by another one', function () { expect( `${fences(returningSuccessfulSnippet)}\n${fences( 'theErrorMessage', 'output' )}\n${fences(synchronousSuccessfulSnippet)}`, 'to come out as', function () { var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); var globalExpect = global.expect; describe('<inline code>', function () { var expect = globalExpect.clone(); expect.output.preferredWidth = 80; before(async function () { var md = await new Markdown( "```js\nvar blah = 'abc';\nif (blah === 'abc') {\n return expect.promise(function (resolve, reject) {\n setImmediate(resolve);\n });\n} else {\n return 456;\n}\n\n```\n\n```output\ntheErrorMessage\n```\n\n```js\nvar foo = 'abc';\nexpect(foo, 'to equal', 'abc');\n\n```\n", { marker: 'unexpected-markdown', capture: 'return', pwdPath: '<projectPath>', fileGlobals: { expect: () => expect.clone() }, } ); await md.evaluate(); this.evaluatedSnippets = md.getSnippets(); this.isNextEvaluatedSnippetOutput = (index) => { var nextSnippet = this.evaluatedSnippets.get(index + 1); return !!nextSnippet && nextSnippet.lang === 'output'; }; }); it('example #1 (<inline code>:2:1) should succeed', function () { var snippetIndex = 0; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail( `snippet evaluation caused an error\n\n${output.text}` ); } }); it('example #2 (<inline code>:14:1) should succeed with the correct output', function () { var snippetIndex = 1; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var writtenOutput = 'theErrorMessage'; expect(evaluatedSnippet.output.text, 'to equal', writtenOutput); }); it('example #3 (<inline code>:18:1) should succeed', function () { var snippetIndex = 2; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail( `snippet evaluation caused an error\n\n${output.text}` ); } }); }); } ); }); it('should convert a synchronously succeeding snippet followed by another one', function () { expect( `${fences(synchronousSuccessfulSnippet)}\n${fences( synchronousThrowingSnippet )}`, 'to come out as', function () { var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); var globalExpect = global.expect; describe('<inline code>', function () { var expect = globalExpect.clone(); expect.output.preferredWidth = 80; before(async function () { var md = await new Markdown( "```js\nvar foo = 'abc';\nexpect(foo, 'to equal', 'abc');\n\n```\n\n```js\nvar bar = 'abc';\nexpect(bar, 'to equal', 'def');\n\n```\n", { marker: 'unexpected-markdown', capture: 'return', pwdPath: '<projectPath>', fileGlobals: { expect: () => expect.clone() }, } ); await md.evaluate(); this.evaluatedSnippets = md.getSnippets(); this.isNextEvaluatedSnippetOutput = (index) => { var nextSnippet = this.evaluatedSnippets.get(index + 1); return !!nextSnippet && nextSnippet.lang === 'output'; }; }); it('example #1 (<inline code>:2:1) should succeed', function () { var snippetIndex = 0; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail( `snippet evaluation caused an error\n\n${output.text}` ); } }); it('example #2 (<inline code>:8:1) should succeed', function () { var snippetIndex = 1; var currentSnippet = this.evaluatedSnippets.get(snippetIndex); var evaluatedSnippet = this.evaluatedSnippets.get( currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex ); var { output } = evaluatedSnippet; if (output === null) { throw new Error('snippet was not correctly evaluted'); } if ( output.kind === 'error' && !this.isNextEvaluatedSnippetOutput(snippetIndex) ) { expect.fail( `snippet evaluation caused an error\n\n${output.text}` ); } }); }); } ); }); describe('when a block is evaluated', function () { it('should output a diff for a mismatch between evaluated output and the markdown', function () { const input = convertMarkdownToMocha( `${fences('return 456;')}\n${fences('123', 'output')}` ).code; const env = createTestRunnerEnvironment(codeToString(input)); expect( () => vm.runInNewContext(env.code, env.context), 'to be rejected with', "expected '456' to equal '123'\n\n-456\n+123" ); }); }); });