From 72762c1ca74ca1353e19fd4640629b301ad888b5 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Fri, 27 Mar 2020 19:20:01 +0100 Subject: [PATCH 01/10] Port the module to evaldown. This commit also makes node 10 the minimum supported version. --- .travis.yml | 3 +- lib/Snippets.js | 51 ---------- lib/UnexpectedMarkdown.js | 102 ------------------- lib/convertMarkdownToMocha.js | 16 ++- lib/createExpect.js | 8 +- lib/evaluateSnippets.js | 140 -------------------------- lib/extractSnippets.js | 58 ----------- lib/magicpenDarkSyntaxTheme.js | 34 ------- lib/magicpenGithubSyntaxTheme.js | 34 ------- lib/snippetRegexp.js | 1 - package.json | 1 + test/Snippets.spec.js | 36 ------- test/UnexpectedMarkdown.spec.js | 138 ------------------------- test/cleanStackTrace.spec.js | 58 ----------- test/evaluateSnippets.spec.js | 114 --------------------- test/extractSnippets.spec.js | 168 ------------------------------- 16 files changed, 15 insertions(+), 947 deletions(-) delete mode 100644 lib/Snippets.js delete mode 100644 lib/evaluateSnippets.js delete mode 100644 lib/extractSnippets.js delete mode 100644 lib/magicpenDarkSyntaxTheme.js delete mode 100644 lib/magicpenGithubSyntaxTheme.js delete mode 100644 lib/snippetRegexp.js delete mode 100644 test/Snippets.spec.js delete mode 100644 test/UnexpectedMarkdown.spec.js delete mode 100644 test/cleanStackTrace.spec.js delete mode 100644 test/evaluateSnippets.spec.js delete mode 100644 test/extractSnippets.spec.js diff --git a/.travis.yml b/.travis.yml index a10186b..7434486 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: node_js env: - TARGET=test node_js: - - '8' - '10' - '12' - 'node' @@ -13,7 +12,7 @@ matrix: env: - TARGET=lint - name: Coverage - node_js: '8' + node_js: '10' env: - TARGET=coverage script: npm run $TARGET diff --git a/lib/Snippets.js b/lib/Snippets.js deleted file mode 100644 index 320e810..0000000 --- a/lib/Snippets.js +++ /dev/null @@ -1,51 +0,0 @@ -var extractSnippets = require('./extractSnippets'); -var evaluateSnippets = require('./evaluateSnippets'); - -class Snippets { - constructor(snippets) { - this.items = snippets; - } - - async evaluate(options) { - await evaluateSnippets(this.items, options); - return this; - } - - get(index) { - return this.items[index]; - } - - getTests() { - var tests = []; - var evaluatedExampleIndex; - - for (const [index, snippet] of this.items.entries()) { - var flags = snippet.flags; - - switch (snippet.lang) { - case 'javascript': - if (flags.evaluate) { - evaluatedExampleIndex = index; - tests.push({ - code: snippet.code, - flags: flags, - }); - } - break; - case 'output': - if (evaluatedExampleIndex === index - 1) { - tests[tests.length - 1].output = snippet.code; - } - break; - } - } - - return tests; - } -} - -module.exports = { - fromMarkdown: function (markdown) { - return new Snippets(extractSnippets(markdown)); - }, -}; diff --git a/lib/UnexpectedMarkdown.js b/lib/UnexpectedMarkdown.js index ead4677..2ec5fd4 100644 --- a/lib/UnexpectedMarkdown.js +++ b/lib/UnexpectedMarkdown.js @@ -1,11 +1,6 @@ -var Snippets = require('./Snippets'); -var createExpect = require('./createExpect'); -var snippetRegexp = require('./snippetRegexp'); -var marked = require('marked-papandreou'); var fs = require('fs'); var pathModule = require('path'); var convertMarkdownToMocha = require('./convertMarkdownToMocha'); -var cleanStackTrace = require('./cleanStackTrace'); var maps = {}; @@ -45,100 +40,3 @@ if (sourceMapSupport) { }, }); } - -var styleRegex = /style=".*?"/; - -class UnexpectedMarkdown { - constructor(content) { - if (!(this instanceof UnexpectedMarkdown)) { - return new UnexpectedMarkdown(content); - } - this.content = content; - } - - async toHtml(options) { - var content = this.content; - var expect = createExpect(options); - - const snippets = await this.getSnippets(options); - - var index = 0; - var renderer = new marked.Renderer(); - renderer.code = function (code) { - var snippet = snippets.get(index); - var previousSnippet = snippets.get(index - 1); - index += 1; - var lang = snippet.lang; - if (lang === 'output') { - if (previousSnippet && previousSnippet.lang === 'javascript') { - if (previousSnippet.htmlErrorMessage) { - return previousSnippet.htmlErrorMessage.replace( - styleRegex, - 'class="output"' - ); - } - - return '
'; - } else { - throw new Error( - `No matching javascript block for output:\n${snippet.code}` - ); - } - } - - return expect.output - .clone('html') - .code(code, lang) - .toString() - .replace(styleRegex, `class="code ${this.options.langPrefix}${lang}"`); - }; - - return marked(content, { renderer: renderer, ...options }); - } - - async getSnippets(options) { - if (this.snippets) { - return this.snippets; - } else { - return Snippets.fromMarkdown(this.content).evaluate(options); - } - } - - async withUpdatedExamples(options) { - const snippets = await this.getSnippets(options); - - var index = 0; - var updatedContent = this.content.replace(snippetRegexp, function ( - $0, - htmlComments, - lang - ) { - var currentIndex = index; - index += 1; - var snippet = snippets.get(currentIndex); - if (snippet.lang === 'output') { - var example = snippets.get(currentIndex - 1); - var output = ''; - if (example && example.lang === 'javascript') { - if (example.errorMessage) { - output = example.errorMessage; - if (snippet.flags.cleanStackTrace) { - output = cleanStackTrace(output); - } - } - } - return `${htmlComments || ''}\`\`\`${lang}\n${output}\n\`\`\``; - } else { - return $0; - } - }); - - return new UnexpectedMarkdown(updatedContent); - } - - toString() { - return this.content; - } -} - -module.exports = UnexpectedMarkdown; diff --git a/lib/convertMarkdownToMocha.js b/lib/convertMarkdownToMocha.js index a3f7eac..fac993e 100644 --- a/lib/convertMarkdownToMocha.js +++ b/lib/convertMarkdownToMocha.js @@ -1,11 +1,13 @@ /* global unexpected:true */ +var fs = require('fs'); var pathModule = require('path'); var esprima = require('esprima'); var escodegen = require('escodegen'); -var Snippets = require('./Snippets'); +var { Markdown } = require('evaldown'); + var locateBabelrc = require('./locateBabelrc'); -var fs = require('fs'); var cleanStackTrace = require('./cleanStackTrace'); +var createExpect = require('./createExpect'); var transpile; var hasBabel = false; @@ -124,12 +126,18 @@ function countLinesUntilIndex(text, untilIndex) { function findCodeBlocks(mdSrc) { var codeBlocks = []; - const snippets = Snippets.fromMarkdown(mdSrc); + const snippets = new Markdown(mdSrc, { + marker: 'unexpected-markdown', + transpileFn: hasBabel ? transpile : null, + customGlobals: { + expect: () => createExpect(), + }, + }).getSnippets(); for (const codeBlock of snippets.items) { codeBlock.lineNumber = 1 + countLinesUntilIndex(mdSrc, codeBlock.index); if (codeBlock.lang === 'output') { var lastJavaScriptBlock = codeBlocks[codeBlocks.length - 1]; - if (!lastJavaScriptBlock || 'output' in lastJavaScriptBlock) { + if (!lastJavaScriptBlock || lastJavaScriptBlock.output) { throw new Error('output block must follow code block'); } lastJavaScriptBlock.output = codeBlock.code; diff --git a/lib/createExpect.js b/lib/createExpect.js index ef71d2b..b5c3a94 100644 --- a/lib/createExpect.js +++ b/lib/createExpect.js @@ -18,12 +18,6 @@ module.exports = function (options) { if (typeof options.indentationWidth === 'number') { expect.output.indentationWidth = options.indentationWidth; } - expect.installPlugin(require('magicpen-prism')); - var themePlugin = - options.theme === 'dark' - ? require('./magicpenDarkSyntaxTheme') - : require('./magicpenGithubSyntaxTheme'); - - return expect.installPlugin(themePlugin); + return expect; }; diff --git a/lib/evaluateSnippets.js b/lib/evaluateSnippets.js deleted file mode 100644 index 2d942f6..0000000 --- a/lib/evaluateSnippets.js +++ /dev/null @@ -1,140 +0,0 @@ -var vm = require('vm'); -var fs = require('fs'); -var createExpect = require('./createExpect'); -var locateBabelrc = require('./locateBabelrc'); - -var transpile; -var hasBabel = false; -try { - var babelCore = require.main.require('babel-core'); - var babelrc = locateBabelrc(); - var babelOptions = JSON.parse(fs.readFileSync(babelrc, 'utf-8')); - hasBabel = true; - - transpile = function transpile(code) { - var babelResult = babelCore.transform(code, { - ...babelOptions, - sourceMaps: false, - compact: false, - }); - - return babelResult.code.replace(/'use strict';/, ''); - }; -} catch (e) { - transpile = function transpile(code) { - // Avoid "Identifier '...' has already been declared" - return code.replace(/\b(?:const|let)\s/g, 'var '); - }; -} - -function isPromise(value) { - return value && typeof value.then === 'function'; -} - -function getErrorMessage(expect, format, error) { - if (error) { - if (error.getErrorMessage) { - var expectForOutput = error.expect || expect; - var output = expectForOutput.createOutput - ? expectForOutput.createOutput(format) - : expectForOutput.output.clone(format); - return error.getErrorMessage({ output: output }).toString(format); - } else if (error.output) { - return error.output.toString(format); - } else { - return expect.output - .clone(format) - .error((error && error.message) || '') - .toString(format); - } - } else { - return ''; - } -} - -module.exports = async function (snippets, options) { - var oldGlobal = Object.assign({}, global); - var expect = createExpect(options); - global.expect = expect.clone(); - global.require = require; - - if (hasBabel) { - var preambleSeparator = - '\n//---------------------preamble----------------------\n'; - var separator = '\n//---------------------separator---------------------\n'; - - var exampleSnippets = snippets.filter(function (snippet) { - return snippet.lang === 'javascript' && snippet.flags.evaluate; - }); - - if (exampleSnippets.length) { - var codeForTranspilation = - preambleSeparator + - exampleSnippets - .map(function (snippet) { - return snippet.flags.async - ? `(function () {${snippet.code}})();` - : snippet.code; - }) - .join(separator); - - var transpiledCode = transpile(codeForTranspilation); - var transpiledSnippets = transpiledCode.split( - new RegExp(`${preambleSeparator}|${separator}`) - ); - - var preamble = transpiledSnippets[0]; - const remainingSnippets = transpiledSnippets.slice(1); - - vm.runInThisContext(preamble); - - for (const [i, transpiledSnippet] of remainingSnippets.entries()) { - exampleSnippets[i].code = transpiledSnippet; - } - } - } - - for (const snippet of snippets) { - if (snippet.lang === 'javascript' && snippet.flags.evaluate) { - try { - if (snippet.flags.freshExpect) { - global.expect = expect.clone(); - } - - if (snippet.flags.async) { - var promise = vm.runInThisContext( - hasBabel - ? snippet.code - : `(function () {${transpile(snippet.code)}})();` - ); - - if (!isPromise(promise)) { - throw new Error( - `Async code block did not return a promise or throw\n${snippet.code}` - ); - } - - await promise; - } else { - vm.runInThisContext( - hasBabel ? snippet.code : transpile(snippet.code) - ); - } - } catch (e) { - snippet.htmlErrorMessage = getErrorMessage(global.expect, 'html', e); - snippet.errorMessage = getErrorMessage(global.expect, 'text', e); - } - } - } - - for (const key of Object.keys(global)) { - if (!(key in oldGlobal)) { - delete global[key]; - } - } - - // inline function used for compatibility with node 8 - for (const key of Object.keys(oldGlobal)) { - global[key] = oldGlobal[key]; - } -}; diff --git a/lib/extractSnippets.js b/lib/extractSnippets.js deleted file mode 100644 index 5f24f60..0000000 --- a/lib/extractSnippets.js +++ /dev/null @@ -1,58 +0,0 @@ -var snippetRegexp = require('./snippetRegexp'); - -function parseFlags(flagsString) { - var flags = {}; - for (const flagString of flagsString.split(/,/)) { - var m = /(\w+)\s*:\s*(\w+)/.exec(flagString.trim()); - flags[m[1]] = m[2] === 'true'; - } - return flags; -} - -function parseSnippetInfo(lang) { - var m = /^(\w+)#(\w+:\w+(,\w+:\w+)*)/.exec(lang); - var flags = {}; - if (m) { - lang = m[1]; - Object.assign(flags, parseFlags(m[2])); - } - - if (lang === 'js') { - lang = 'javascript'; - } - - return { - lang: lang, - flags: flags, - }; -} - -module.exports = function (markdown) { - var snippets = []; - var m; - while ((m = snippetRegexp.exec(markdown))) { - var snippetInfo = parseSnippetInfo(m[2]); - snippetInfo.index = m.index; - if (m[1]) { - snippetInfo.index += m[1].length; - - const matchHtmlComments = m[1].match( - /^/gm - ); - if (matchHtmlComments) { - for (const htmlCommentValue of Array.from(matchHtmlComments)) { - snippetInfo.flags = Object.assign( - parseFlags(htmlCommentValue), - snippetInfo.flags - ); - } - } - } - if (typeof snippetInfo.flags.evaluate !== 'boolean') { - snippetInfo.flags.evaluate = true; - } - snippetInfo.code = m[3]; - snippets.push(snippetInfo); - } - return snippets; -}; diff --git a/lib/magicpenDarkSyntaxTheme.js b/lib/magicpenDarkSyntaxTheme.js deleted file mode 100644 index c74f4fa..0000000 --- a/lib/magicpenDarkSyntaxTheme.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - name: 'dark-syntax-theme', - installInto: function (pen) { - pen.installTheme('html', { - jsComment: '#888', - jsFunctionName: 'jsKeyword', - jsKeyword: '#AE81FF', - jsNumber: '#66D9EF', - jsPrimitive: 'white', - jsRegexp: '#AE81FF', - jsString: '#E6DB74', - jsKey: '#CCC', - - error: ['#F92672', 'bold'], - success: ['#A6E22E', 'bold'], - diffAddedLine: '#A6E22E', - diffAddedHighlight: ['bg#A6E22E', 'black'], - diffAddedSpecialChar: ['bg#A6E22E', 'cyan', 'bold'], - diffRemovedLine: '#F92672', - diffRemovedHighlight: ['bg#F92672', 'black'], - diffRemovedSpecialChar: ['bg#F92672', 'cyan', 'bold'], - partialMatchHighlight: 'bg#E6DB74', - - prismComment: 'jsComment', - prismSymbol: ['#66D9EF', 'bold'], - prismString: 'jsString', - prismOperator: '#F92672', - prismKeyword: '#F92672', - prismRegex: '#AE81FF', - - prismFunction: [], - }); - }, -}; diff --git a/lib/magicpenGithubSyntaxTheme.js b/lib/magicpenGithubSyntaxTheme.js deleted file mode 100644 index 0f0ee6a..0000000 --- a/lib/magicpenGithubSyntaxTheme.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - name: 'github-syntax-theme', - installInto: function (pen) { - pen.installTheme('html', { - // Mimic github theme - prismComment: 'jsComment', - prismCdata: '#708090', - - prismPunctuation: '#000000', - - prismTag: '#63a35c', - prismSymbol: '#0086b3', - - prismAttrname: '#795da3', - - prismOperator: 'jsKeyword', - prismVariable: '#a67f59', - - prismString: 'jsString', - prismUrl: 'prismString', - cssString: 'prismString', - prismEntity: 'prismString', - prismAtrule: 'prismString', - prismAttrValue: 'prismString', - prismRegex: 'jsRegexp', - - prismKeyword: 'jsKeyword', - - prismFunction: '#000000', - - prismImportant: ['#0086b3', 'bold'], - }); - }, -}; diff --git a/lib/snippetRegexp.js b/lib/snippetRegexp.js deleted file mode 100644 index ae386e7..0000000 --- a/lib/snippetRegexp.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = /((?:^\s*)*)^```(\S*)\n([\s\S]*?)\n```/gm; diff --git a/package.json b/package.json index d7e51b8..f800d3f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dependencies": { "escodegen": "^1.11.0", "esprima": "^4.0.1", + "evaldown": "^0.4.0", "magicpen-prism": "^4.0.0", "marked-papandreou": "^0.3.3-patch3", "source-map": "^0.5.1", diff --git a/test/Snippets.spec.js b/test/Snippets.spec.js deleted file mode 100644 index c6722a7..0000000 --- a/test/Snippets.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -const expect = require('unexpected'); - -const Snippets = require('../lib/Snippets'); - -describe('Snippets', () => { - describe('#getTests()', () => { - it('should test block for each javascript/output pair', async () => { - const snippets = Snippets.fromMarkdown( - [ - 'Asserts deep equality.', - '', - '```javascript', - 'throw new Error("foo\\n at bar (/somewhere.js:1:2)\\n at quux (/blah.js:3:4)\\n at baz (/yadda.js:5:6)")', - '```', - '', - '', - '```output', - 'foo', - ' at bar (/path/to/file.js:x:y)', - ' at quux (/path/to/file.js:x:y)', - '```', - ].join('\n') - ); - - expect(await snippets.getTests(), 'to equal', [ - { - code: - 'throw new Error("foo\\n at bar (/somewhere.js:1:2)\\n at quux (/blah.js:3:4)\\n at baz (/yadda.js:5:6)")', - flags: { evaluate: true }, - output: - 'foo\n at bar (/path/to/file.js:x:y)\n at quux (/path/to/file.js:x:y)', - }, - ]); - }); - }); -}); diff --git a/test/UnexpectedMarkdown.spec.js b/test/UnexpectedMarkdown.spec.js deleted file mode 100644 index acde2df..0000000 --- a/test/UnexpectedMarkdown.spec.js +++ /dev/null @@ -1,138 +0,0 @@ -var expect = require('unexpected'); -var UnexpectedMarkdown = require('../lib/UnexpectedMarkdown'); - -describe('UnexpectedMarkdown', function () { - var markdown; - beforeEach(function () { - markdown = new UnexpectedMarkdown( - [ - 'Asserts deep equality.', - '', - '```javascript', - "expect({ a: 'b' }, 'to equal', { a: 1234 });", - 'var now = new Date();', - "expect(now, 'to equal', now);", - "expect(now, 'to equal', new Date(now.getTime()));", - "expect({ now: now }, 'to equal', { now: now });", - '```', - '', - 'For a lot of types a failing equality test results in a nice', - 'diff. Below you can see an object diff.', - '', - '```javascript', - "expect({ text: 'foo!' }, 'to equal', { text: 'f00!' });", - '```', - '', - '```output', - 'Missing output', - '```', - ].join('\n') - ); - }); - - describe('toHtml', function () { - it('syntax highlight examples (dark)', function () { - return expect( - markdown.toHtml({ theme: 'dark' }), - 'when fulfilled', - 'to contain', - '1234' - ); - }); - - it('outputs highlight examples (light)', function () { - return expect( - markdown.toHtml({ theme: 'light' }), - 'when fulfilled', - 'to contain', - 'f00' - ); - }); - }); - - describe('withUpdatedExamples', function () { - it('produces a markdown where the examples has been updated', function () { - return expect( - markdown.withUpdatedExamples({}), - 'when fulfilled', - expect.it((markdown) => - expect( - markdown.toString(), - 'to contain', - [ - '```output', - "expected { text: 'foo!' } to equal { text: 'f00!' }", - '', - '{', - " text: 'foo!' // should equal 'f00!'", - ' //', - ' // -foo!', - ' // +f00!', - '}', - '```', - ].join('\n') - ) - ) - ); - }); - - it('should work correctly with async snippets that reject', async () => { - const markdown = new UnexpectedMarkdown( - [ - '', - '```javascript', - "return Promise.reject(new Error('boom'));", - '```', - '', - '```output', - 'Missing output', - '```', - ].join('\n') - ); - - const updatedMarkdown = await markdown.withUpdatedExamples({}); - - expect( - updatedMarkdown.content, - 'to contain', - ['```output', 'boom', '```'].join('\n') - ); - }); - }); - - it('produces a markdown where the examples has been updated', function () { - const markdown = new UnexpectedMarkdown( - [ - 'Asserts deep equality.', - '', - '```javascript', - 'throw new Error("foo\\n at bar (/somewhere.js:1:2)\\n at quux (/blah.js:3:4)\\n at baz (/yadda.js:5:6)")', - '```', - '', - '', - '```output', - 'Missing output', - '```', - ].join('\n') - ); - - return expect( - markdown.withUpdatedExamples({}), - 'when fulfilled', - expect.it((markdown) => - expect( - markdown.toString(), - 'to contain', - [ - '', - '```output', - 'foo', - ' at bar (/path/to/file.js:x:y)', - ' at quux (/path/to/file.js:x:y)', - '```', - ].join('\n') - ) - ) - ); - }); -}); diff --git a/test/cleanStackTrace.spec.js b/test/cleanStackTrace.spec.js deleted file mode 100644 index 3fc4cea..0000000 --- a/test/cleanStackTrace.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -const expect = require('unexpected'); -const cleanStackTrace = require('../lib/cleanStackTrace'); - -describe('cleanStackTrace', function () { - it('should turn line numbers into :x:y', function () { - expect( - cleanStackTrace('foo\n at bar (/somewhere.js:1:2)'), - 'to contain', - ':x:y' - ); - }); - - it('should handle a stack trace with evalmachine.', function () { - expect( - cleanStackTrace('foo\n at bar (evalmachine.:3:4)'), - 'to equal', - 'foo\n at bar (/path/to/file.js:x:y)' - ); - }); - - it('should handle a stack trace with just a path', function () { - expect( - cleanStackTrace('foo\n at /foo/bar.js:3:4'), - 'to equal', - 'foo\n at /path/to/file.js:x:y' - ); - }); - - it('should only preserve 2 stack locations per trace', function () { - expect( - cleanStackTrace( - [ - 'foo', - ' at bar (/a.js:1:2)', - ' at baz (/a.js:1:2)', - ' at quux (/a.js:1:2)', - 'something else', - 'foo', - ' at bar (/a.js:1:2)', - ' at baz (/a.js:1:2)', - ' at quux (/a.js:1:2)', - 'hey', - ].join('\n') - ), - 'to equal', - [ - 'foo', - ' at bar (/path/to/file.js:x:y)', - ' at baz (/path/to/file.js:x:y)', - 'something else', - 'foo', - ' at bar (/path/to/file.js:x:y)', - ' at baz (/path/to/file.js:x:y)', - 'hey', - ].join('\n') - ); - }); -}); diff --git a/test/evaluateSnippets.spec.js b/test/evaluateSnippets.spec.js deleted file mode 100644 index 388b430..0000000 --- a/test/evaluateSnippets.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -const expect = require('unexpected'); - -const evaluateSnippets = require('../lib/evaluateSnippets'); - -describe('extractSnippets', () => { - it('should evaluate javascript snippets', async () => { - const snippets = [ - { - lang: 'javascript', - flags: { evaluate: true }, - index: 24, - code: - 'throw new Error("foo\\n at bar (/somewhere.js:1:2)\\n at quux (/blah.js:3:4)\\n at baz (/yadda.js:5:6)")', - }, - { - lang: 'output', - flags: { cleanStackTrace: true, evaluate: true }, - index: 198, - code: - 'foo\n at bar (/path/to/file.js:x:y)\n at quux (/path/to/file.js:x:y)', - }, - ]; - - await evaluateSnippets(snippets); - - expect(snippets[0], 'to satisfy', { - htmlErrorMessage: - '
foo
  at bar (/somewhere.js:1:2)
  at quux (/blah.js:3:4)
  at baz (/yadda.js:5:6)
', - errorMessage: - 'foo\n at bar (/somewhere.js:1:2)\n at quux (/blah.js:3:4)\n at baz (/yadda.js:5:6)', - }); - }); - - describe('with an aync snippet', () => { - it('should evaluate javascript snippets', async () => { - const snippets = [ - { - lang: 'javascript', - flags: { async: true, evaluate: true }, - index: 40, - code: "return Promise.reject(new Error('boom'));", - }, - ]; - - await evaluateSnippets(snippets); - - expect(snippets[0], 'to satisfy', { - htmlErrorMessage: - '
boom
', - errorMessage: 'boom', - }); - }); - - it('should record an error string if missing a return', async () => { - const snippets = [ - { - lang: 'javascript', - flags: { async: true, evaluate: true }, - index: 40, - code: 'Promise.resolve();', - }, - ]; - - await evaluateSnippets(snippets); - - expect(snippets[0], 'to satisfy', { - htmlErrorMessage: - '
Async code block did not return a promise or throw
Promise.resolve();
', - errorMessage: - 'Async code block did not return a promise or throw\nPromise.resolve();', - }); - }); - }); - - it('should clone the output from the right expect when rendering the error message', async () => { - const unexpected = expect.clone().use((expect) => { - expect = expect.child(); - expect.addStyle('fancyQuotes', function (str) { - this.red('>>').text(str).red('<<'); - }); - - expect.exportAssertion(' to foo', (expect, subject) => { - expect.subjectOutput = function () { - // Used to fail with TypeError: this.fancyQuotes is not a function - this.fancyQuotes(subject); - }; - expect(subject, 'to equal', 'foo'); - }); - }); - - const snippets = [ - { - lang: 'javascript', - flags: { evaluate: true }, - index: 24, - code: "expect('bar', 'to foo');", - }, - { - lang: 'output', - flags: { cleanStackTrace: true, evaluate: true }, - index: 198, - code: 'expected >>bar<< foo', - }, - ]; - - await evaluateSnippets(snippets, { unexpected }); - - expect(snippets[0], 'to satisfy', { - htmlErrorMessage: - '
expected >>bar<< to foo
 
bar
foo
', - errorMessage: 'expected >>bar<< to foo\n\n-bar\n+foo', - }); - }); -}); diff --git a/test/extractSnippets.spec.js b/test/extractSnippets.spec.js deleted file mode 100644 index adbc542..0000000 --- a/test/extractSnippets.spec.js +++ /dev/null @@ -1,168 +0,0 @@ -const extractSnippets = require('../lib/extractSnippets'); -const expect = require('unexpected'); - -describe('extractSnippets', function () { - it('should extract a snippet between some markdown sections', function () { - expect( - extractSnippets( - '# foo\n\n```javascript\nalert("Hello!");\n```\n\n# bar\n' - ), - 'to satisfy', - [{ code: 'alert("Hello!");' }] - ); - }); - - it('should multiple snippets', function () { - expect( - extractSnippets( - '```js\nalert("Hello!");\n```\n\n```js\nalert("world!");\n```\n' - ), - 'to satisfy', - [{ code: 'alert("Hello!");' }, { code: 'alert("world!");' }] - ); - }); - - it('should normalize js to javascript', function () { - expect(extractSnippets('```js\nalert("Hello!");\n```\n'), 'to satisfy', [ - { lang: 'javascript', code: 'alert("Hello!");' }, - ]); - }); - - it('should default to evaluate:true', function () { - expect(extractSnippets('```js\nalert("Hello!");\n```\n'), 'to satisfy', [ - { flags: { evaluate: true } }, - ]); - }); - - it('should allow changing evaluate to false via js#evaluate:false', function () { - expect( - extractSnippets('```js#evaluate:false\nalert("Hello!");\n```\n'), - 'to satisfy', - [{ flags: { evaluate: false } }] - ); - }); - - it('should allow changing evaluate to false via a preceding HTML comment', function () { - expect( - extractSnippets( - '\n```js\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { evaluate: false } }] - ); - }); - - it('should extract a flag after the language specifier and #', function () { - expect( - extractSnippets('```js#foo:true\nalert("Hello!");\n```\n'), - 'to satisfy', - [{ flags: { foo: true } }] - ); - }); - - it('should extract multiple comma-separated flags after the language specifier and #', function () { - expect( - extractSnippets('```js#foo:true,bar:false\nalert("Hello!");\n```\n'), - 'to satisfy', - [{ flags: { foo: true, bar: false } }] - ); - }); - - it('should extract a flag from a preceding HTML comment', function () { - expect( - extractSnippets( - '\n```js\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: true } }] - ); - }); - - it('should extract multiple comma-separated flags from a preceding HTML comment', function () { - expect( - extractSnippets( - '\n```js\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: true, bar: false } }] - ); - }); - - it('should extract flags from multiple preceding HTML comments', function () { - expect( - extractSnippets( - '\n\n\n```js\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: true, bar: true, quux: true } }] - ); - }); - - it('should not extract flags from preceding HTML comments that do not start with the unexpected-markdown marker', function () { - expect( - extractSnippets( - '\n\n\n```js\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: true, bar: undefined, quux: undefined } }] - ); - }); - - it('should tolerate whitespace between the flags and commans in the preceding HTML comment', function () { - expect( - extractSnippets( - '\n```js\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: true, bar: false } }] - ); - }); - - it('should extract flags from both a preceding HTML command and after the language specifier and #', function () { - expect( - extractSnippets( - '\n```js#bar:false\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: true, bar: false } }] - ); - }); - - it('should let the flags after the language specifier win when the same flag is provided in both places', function () { - expect( - extractSnippets( - '\n```js#foo:false\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ flags: { foo: false } }] - ); - }); - - it('provides the index of the block', function () { - expect( - extractSnippets('foobar\n```js#foo:false\nalert("Hello!");\n```\n'), - 'to satisfy', - [{ index: 7 }] - ); - }); - - it('skips past a preceding HTML comment when providing the index', function () { - expect( - extractSnippets( - 'foobar\n\n```js#foo:false\nalert("Hello!");\n```\n' - ), - 'to satisfy', - [{ index: 25 }] - ); - }); - - it('captures flag before output blocks', function () { - expect( - extractSnippets( - '```js\nalert("Hello!");\n```\n\n\n```output\nthe output\n```\n' - ), - 'to satisfy', - [{ lang: 'javascript' }, { lang: 'output', flags: { foo: true } }] - ); - }); -}); From e926faaabcc416d45e8bdde5a78f0ecdd86e94e9 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sat, 25 Apr 2020 21:34:53 +0200 Subject: [PATCH 02/10] Remove the now unusued createExpect() function. --- lib/convertMarkdownToMocha.js | 5 ----- lib/createExpect.js | 23 ----------------------- 2 files changed, 28 deletions(-) delete mode 100644 lib/createExpect.js diff --git a/lib/convertMarkdownToMocha.js b/lib/convertMarkdownToMocha.js index fac993e..e9e3712 100644 --- a/lib/convertMarkdownToMocha.js +++ b/lib/convertMarkdownToMocha.js @@ -7,7 +7,6 @@ var { Markdown } = require('evaldown'); var locateBabelrc = require('./locateBabelrc'); var cleanStackTrace = require('./cleanStackTrace'); -var createExpect = require('./createExpect'); var transpile; var hasBabel = false; @@ -128,10 +127,6 @@ function findCodeBlocks(mdSrc) { var codeBlocks = []; const snippets = new Markdown(mdSrc, { marker: 'unexpected-markdown', - transpileFn: hasBabel ? transpile : null, - customGlobals: { - expect: () => createExpect(), - }, }).getSnippets(); for (const codeBlock of snippets.items) { codeBlock.lineNumber = 1 + countLinesUntilIndex(mdSrc, codeBlock.index); diff --git a/lib/createExpect.js b/lib/createExpect.js deleted file mode 100644 index b5c3a94..0000000 --- a/lib/createExpect.js +++ /dev/null @@ -1,23 +0,0 @@ -/* global unexpected */ -module.exports = function (options) { - options = { ...options }; - var expect; - if (options.unexpected) { - expect = options.unexpected.clone(); - } else if (typeof unexpected === 'undefined') { - expect = require('unexpected').clone(); - expect.output.preferredWidth = 80; - } else { - expect = unexpected.clone(); - } - - if (options.preferredWidth) { - expect.output.preferredWidth = options.preferredWidth; - } - - if (typeof options.indentationWidth === 'number') { - expect.output.indentationWidth = options.indentationWidth; - } - - return expect; -}; From 2954f9bb88df5802375de35071e7ba60507b1cf9 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sun, 26 Apr 2020 11:01:22 +0200 Subject: [PATCH 03/10] Rewrite unexpected-markdown to remove its duplicate runtime. As evaldown evolved and the docs started to rely on its new behaviour problems arose with unexpected-markdown.In particular, this module attempted to replicate the behaviour of snippet evaluation and was effectively a separate implementation of it which meant it became out of sync. In order to avoid this situation going forward, rewrite the module to make use of the same runtime and merely assert that afterwards the evaluated result matches the state of the on-disk markdown file. --- bootstrap-unexpected-markdown.js | 3 + lib/convertMarkdownToMocha.js | 440 +++++----------- package.json | 2 +- test/convertMarkdownToMocha.spec.js | 761 ++++++++-------------------- 4 files changed, 360 insertions(+), 846 deletions(-) create mode 100644 bootstrap-unexpected-markdown.js diff --git a/bootstrap-unexpected-markdown.js b/bootstrap-unexpected-markdown.js new file mode 100644 index 0000000..9f27baf --- /dev/null +++ b/bootstrap-unexpected-markdown.js @@ -0,0 +1,3 @@ +/* global expect:true */ +expect = require('unexpected').clone(); +expect.output.preferredWidth = 80; diff --git a/lib/convertMarkdownToMocha.js b/lib/convertMarkdownToMocha.js index e9e3712..b3d4fa2 100644 --- a/lib/convertMarkdownToMocha.js +++ b/lib/convertMarkdownToMocha.js @@ -1,118 +1,26 @@ -/* global unexpected:true */ -var fs = require('fs'); var pathModule = require('path'); var esprima = require('esprima'); var escodegen = require('escodegen'); var { Markdown } = require('evaldown'); -var locateBabelrc = require('./locateBabelrc'); -var cleanStackTrace = require('./cleanStackTrace'); +var resolvedPathToEvaldown = require.resolve('evaldown'); -var transpile; -var hasBabel = false; -try { - var babelCore = require.main.require('babel-core'); - var babelrc = locateBabelrc(); - var babelOptions = JSON.parse(fs.readFileSync(babelrc, 'utf-8')); - hasBabel = true; +function checkCodeBlockSkipped(codeBlock) { + const { lang, flags } = codeBlock; - transpile = function transpile(code) { - var babelResult = babelCore.transform(code, { - ...babelOptions, - sourceMaps: false, - compact: false, - }); + if (!(lang === 'javascript' || lang === 'output')) { + return true; + } else if (lang === 'javascript' && !flags.evaluate) { + return true; + } - return babelResult.code.replace(/'use strict';/, ''); - }; -} catch (e) { - transpile = function transpile(code) { - return code; - }; + return false; } function parseFunctionBody(fn) { return esprima.parse(fn.toString()).body[0].body.body; } -function instrumentReturns(astNode, exampleNumber) { - if (Array.isArray(astNode)) { - for (var i = 0; i < astNode.length; i += 1) { - var statement = astNode[i]; - if (statement.type === 'ReturnStatement') { - astNode.splice( - i, - 1, - { - type: 'ExpressionStatement', - expression: { - type: 'AssignmentExpression', - operator: '=', - left: { - type: 'Identifier', - name: `__returnValue${exampleNumber}`, - }, - right: statement.argument, - }, - }, - { - type: 'BreakStatement', - label: { - type: 'Identifier', - name: `example${exampleNumber}`, - }, - } - ); - } else if (statement.type === 'IfStatement') { - instrumentReturns(statement.consequent, exampleNumber); - instrumentReturns(statement.alternate, exampleNumber); - } - } - } else if (astNode && typeof astNode === 'object') { - if (astNode.type === 'BlockStatement') { - instrumentReturns(astNode.body, exampleNumber); - } - } -} - -function makeTryCatchConstruct(exampleNumber, topLevelStatements) { - /* eslint-disable */ - var tryCatch = parseFunctionBody( - /* istanbul ignore next */ - function f() { - var __returnValueX; - exampleX: try { - } catch (err) { - return endOfExampleX(err); - } - if (isPromise(__returnValueX)) { - return __returnValueX.then(function () { - return endOfExampleX(); - }, endOfExampleX); - } else { - return endOfExampleX(); - } - function endOfExampleX(err) {} - } - ); - /* eslint-enable */ - - tryCatch[0].declarations[0].id.name = `__returnValue${exampleNumber}`; - tryCatch[1].label.name = `example${exampleNumber}`; - tryCatch[1].body.handler.body.body[0].argument.callee.name = `endOfExample${exampleNumber}`; - tryCatch[2].test.arguments[0].name = `__returnValue${exampleNumber}`; - tryCatch[2].consequent.body[0].argument.callee.object.name = `__returnValue${exampleNumber}`; - tryCatch[2].consequent.body[0].argument.arguments[1].name = `endOfExample${exampleNumber}`; - tryCatch[2].consequent.body[0].argument.arguments[0].body.body[0].argument.callee.name = `endOfExample${exampleNumber}`; - tryCatch[2].alternate.body[0].argument.callee.name = `endOfExample${exampleNumber}`; - tryCatch[3].id.name = `endOfExample${exampleNumber}`; - - instrumentReturns(topLevelStatements, exampleNumber); - - Array.prototype.push.apply(tryCatch[1].body.block.body, topLevelStatements); - return tryCatch; -} - function countLinesUntilIndex(text, untilIndex) { var count = 0; text.replace(/\r\n?|\n\r?/g, function ($0, index) { @@ -124,151 +32,23 @@ function countLinesUntilIndex(text, untilIndex) { } function findCodeBlocks(mdSrc) { - var codeBlocks = []; const snippets = new Markdown(mdSrc, { marker: 'unexpected-markdown', }).getSnippets(); for (const codeBlock of snippets.items) { codeBlock.lineNumber = 1 + countLinesUntilIndex(mdSrc, codeBlock.index); - if (codeBlock.lang === 'output') { - var lastJavaScriptBlock = codeBlocks[codeBlocks.length - 1]; - if (!lastJavaScriptBlock || lastJavaScriptBlock.output) { - throw new Error('output block must follow code block'); - } - lastJavaScriptBlock.output = codeBlock.code; - lastJavaScriptBlock.outputFlags = codeBlock.flags || {}; - } else if (codeBlock.lang === 'javascript' && codeBlock.flags.evaluate) { - codeBlocks.push(codeBlock); - } } - return codeBlocks; + return snippets; } -function compileCodeBlocksToAst(codeBlocks, fileName) { - var top = []; - var cursor = top; - for (const [i, codeBlock] of codeBlocks.entries()) { - var previousExampleNumber = i + 1; - var topLevelStatements = esprima.parse( - `${new Array(codeBlock.lineNumber).join('\n')}(function () {\n${ - codeBlock.code - }\n}());`, - { loc: true, source: pathModule.resolve(fileName) } - ).body[0].expression.callee.body.body; - - for (const [ - statementIndex, - topLevelStatement, - ] of topLevelStatements.entries()) { - switch (topLevelStatement.type) { - case 'VariableDeclaration': - topLevelStatement.kind = 'var'; - break; - case 'FunctionDeclaration': - var newStatement = { - loc: topLevelStatement.loc, - type: 'VariableDeclaration', - declarations: [ - { - loc: topLevelStatement.loc, - type: 'VariableDeclarator', - id: topLevelStatement.id, - init: topLevelStatement, - }, - ], - kind: 'var', - }; - topLevelStatements[statementIndex] = newStatement; - break; - } - } - - if (codeBlock.flags.freshExpect) { - Array.prototype.push.apply( - cursor, - parseFunctionBody( - /* istanbul ignore next */ - function f(expect) { - expect = unexpected.clone(); - } - ) - ); - } - var tryCatch = makeTryCatchConstruct( - previousExampleNumber, - topLevelStatements - ); - - Array.prototype.push.apply(cursor, tryCatch); - - cursor = tryCatch[3].body.body; - if (i === codeBlocks.length - 1) { - var check; - if (typeof codeBlock.output === 'string') { - check = parseFunctionBody( - /* istanbul ignore next */ - function f(err, expect) { - if (err) { - var message = err.isUnexpected - ? err.getErrorMessage('text').toString() - : err.message; - - expect(message, 'to equal', 'expectedErrorMessage'); - } else { - throw new Error('expected example to fail'); - } - } - ); - check[0].consequent.body[1].expression.arguments[2].value = - codeBlock.output; - if (codeBlock.outputFlags.cleanStackTrace) { - check[0].consequent.body.splice(1, 0, { - type: 'ExpressionStatement', - expression: { - type: 'AssignmentExpression', - operator: '=', - left: { - type: 'Identifier', - name: 'message', - }, - right: { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'cleanStackTrace', - }, - arguments: [ - { - type: 'Identifier', - name: 'message', - }, - ], - }, - }, - }); - check.unshift(esprima.parse(cleanStackTrace.toString()).body[0]); - } - } else { - check = parseFunctionBody( - /* istanbul ignore next */ - function f(err, expect) { - if (err) { - expect.fail(err); - } - } - ); - } - Array.prototype.push.apply(cursor, check); - } - } - return top; -} - -module.exports = function (mdSrc, fileName) { - if (fileName) { - fileName = pathModule.relative(process.cwd(), fileName); +module.exports = function (mdSrc, filePath) { + var fileName; + if (filePath) { + fileName = pathModule.relative(process.cwd(), filePath); + filePath = pathModule.dirname(filePath); } else { fileName = ''; + filePath = process.cwd(); } var codeBlocks = findCodeBlocks(mdSrc); @@ -279,89 +59,149 @@ module.exports = function (mdSrc, fileName) { body: parseFunctionBody( /* istanbul ignore next */ function f() { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } - describe('', function () {}); + var { Markdown } = require(''); + var globalExpect = global.expect; + + describe('', function () { + var expect = globalExpect.clone(); + expect.output.preferredWidth = 80; + + before(async function () { + var md = await new Markdown('', { + marker: 'unexpected-markdown', + capture: 'return', + pwdPath: '', + 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'; + }; + }); + }); } ), }; /* eslint-enable */ - var describeCall = ast.body[2].expression; - - describeCall.arguments[0].value = pathModule.basename(fileName, '.md'); - - var preamble; - if (hasBabel && codeBlocks.length) { - var preambleSeparator = - '\n//---------------------um-preamble----------------------\n'; - var separator = - '\n//---------------------um-separator---------------------\n'; - var transpiledCode = transpile( - preambleSeparator + - codeBlocks - .map(function (codeBlock) { - return codeBlock.flags.async - ? `(function unexpectedMarkdownScope() {${codeBlock.code}})("unexpectedMarkdownScope");` - : codeBlock.code; - }) - .join(separator) - ); + var relativePathToEvaldown = pathModule.relative( + pathModule.dirname(fileName), + resolvedPathToEvaldown + ); - var transpiledBlocks = transpiledCode.split( - new RegExp(`${preambleSeparator}|${separator}`) - ); + var requireCall = ast.body[0].declarations[0].init; + requireCall.arguments[0].value = `./${relativePathToEvaldown}`; - preamble = esprima.parse(transpiledBlocks[0]); - const remainingBlocks = transpiledBlocks.slice(1); + var describeCall = ast.body[2].expression; + describeCall.arguments[0].value = pathModule.basename(fileName, '.md'); - for (const [i, transpiledBlock] of remainingBlocks.entries()) { - if (codeBlocks[i].flags.async) { - codeBlocks[i].code = transpiledBlock - .replace(/^\(function unexpectedMarkdownScope\(\) \{\n/, '') - .replace(/\n\}\)\("unexpectedMarkdownScope"\);$/, '') - .replace(/^ {4}/gm, ''); - } else { - codeBlocks[i].code = transpiledBlock; - } - } - } + var beforeEachCall = describeCall.arguments[1].body.body[2].expression; + var newMarkdown = beforeEachCall.arguments[0].body.body[0].declarations[0]; + // set the mdSrc as the first argument to the Markdown constructor + newMarkdown.init.argument.arguments[0].value = mdSrc; + // set the current file path file as the base of any in markdown require + newMarkdown.init.argument.arguments[1].properties[2].value.value = filePath; for (const [i, codeBlock] of codeBlocks.entries()) { var exampleNumber = i + 1; - /* eslint-disable */ - var itExpressionStatement = parseFunctionBody( - /* istanbul ignore next */ - function f() { - it('', function () { - var expect = unexpected.clone(); - }); - } - )[0]; - /* eslint-enable */ - itExpressionStatement.expression.arguments[0].value = `example #${exampleNumber} (${fileName}:${ + var testName = `example #${exampleNumber} (${fileName}:${ codeBlock.lineNumber + 1 - }:1) should ${ - typeof codeBlock.output === 'string' - ? 'fail with the correct error message' - : 'succeed' - }`; - - if (preamble) { - itExpressionStatement.expression.arguments[1].body.body.push(preamble); + }:1)`; + + let isSkipped = false; + let itExpressionStatement; + if ((isSkipped = checkCodeBlockSkipped(codeBlock))) { + /* eslint-disable */ + itExpressionStatement = parseFunctionBody( + /* istanbul ignore next */ + function f() { + it.skip('', function () {}); + } + )[0]; + /* eslint-enable */ + } else { + /* eslint-disable */ + itExpressionStatement = parseFunctionBody( + /* istanbul ignore next */ + function f() { + it('', function () {}); + } + )[0]; + /* eslint-enable */ + + testName += ` should ${ + codeBlock.lang === 'output' + ? 'succeed with the correct output' + : 'succeed' + }`; } + itExpressionStatement.expression.arguments[0].value = testName; + describeCall.arguments[1].body.body.push(itExpressionStatement); + if (isSkipped) { + continue; + } + + // add deferencing the corresponding evaluated snippet + + /* eslint-disable no-unused-vars */ + var itSnippetStatements = parseFunctionBody(function f() { + var snippetIndex = 0; + var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + }); + /* eslint-enable no-unused-vars */ + itSnippetStatements[0].declarations[0].init.value = i; Array.prototype.push.apply( itExpressionStatement.expression.arguments[1].body.body, - compileCodeBlocksToAst(codeBlocks.slice(0, i + 1), fileName) + itSnippetStatements ); + + if (codeBlock.lang === 'output') { + // assert the output in the markdown file is the same as the evaluated output + + /* eslint-disable no-undef */ + var itOutputStatements = parseFunctionBody(function f() { + var expectedOutput = ''; + expect(expectedOutput, 'to equal', evaluatedSnippet.code); + }); + /* eslint-enable no-undef */ + itOutputStatements[0].declarations[0].init.value = codeBlock.code; + Array.prototype.push.apply( + itExpressionStatement.expression.arguments[1].body.body, + itOutputStatements + ); + } else { + // assert that the snippet executed correctly + + /* eslint-disable no-undef */ + var itEvaluatedSnippets = parseFunctionBody(function f() { + 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}`); + } + }); + /* eslint-enable no-undef */ + + Array.prototype.push.apply( + itExpressionStatement.expression.arguments[1].body.body, + itEvaluatedSnippets + ); + } } return escodegen.generate(ast, { sourceMap: true, sourceMapWithCode: true }); }; diff --git a/package.json b/package.json index f800d3f..89114d7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "escodegen": "^1.11.0", "esprima": "^4.0.1", - "evaldown": "^0.4.0", + "evaldown": "^0.6.0", "magicpen-prism": "^4.0.0", "marked-papandreou": "^0.3.3-patch3", "source-map": "^0.5.1", diff --git a/test/convertMarkdownToMocha.spec.js b/test/convertMarkdownToMocha.spec.js index 4df29ac..47be998 100644 --- a/test/convertMarkdownToMocha.spec.js +++ b/test/convertMarkdownToMocha.spec.js @@ -1,5 +1,4 @@ -/* global unexpected:true */ -/* eslint-disable no-labels, mocha/no-nested-tests, mocha/no-identical-title */ +/* eslint-disable mocha/no-nested-tests, mocha/no-identical-title */ var convertMarkdownToMocha = require('../lib/convertMarkdownToMocha'); var esprima = require('esprima'); @@ -57,43 +56,41 @@ function fences(code, language) { describe('convertMarkdownToMocha', function () { it('should convert a returning snippet expected to be successful', function () { expect(fences(returningSuccessfulSnippet), 'to come out as', function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } + var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); + var globalExpect = global.expect; describe('', 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: '/Users/alex/Documents/projects/unexpected-markdown', + 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 (:2:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var blah = 'abc'; - if (blah === 'abc') { - __returnValue1 = expect.promise(function (resolve, reject) { - setImmediate(resolve); - }); - break example1; - } else { - __returnValue1 = 456; - break example1; - } - } catch (err) { - return endOfExample1(err); + var snippetIndex = 0; + var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + var { output } = evaluatedSnippet; + if (output === null) { + throw new Error('snippet was not correctly evaluted'); } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - expect.fail(err); - } + if ( + output.kind === 'error' && + !this.isNextEvaluatedSnippetOutput(snippetIndex) + ) { + expect.fail(`snippet evaluation caused an error\n\n${output.text}`); } }); }); @@ -108,51 +105,53 @@ describe('convertMarkdownToMocha', function () { )}`, 'to come out as', function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } + var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); + var globalExpect = global.expect; describe('', function () { - it('example #1 (:2:1) should fail with the correct error message', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var blah = 'abc'; - if (blah === 'abc') { - __returnValue1 = expect.promise(function (resolve, reject) { - setImmediate(resolve); - }); - break example1; - } else { - __returnValue1 = 456; - break example1; - } - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - var message = err.isUnexpected - ? err.getErrorMessage('text').toString() - : err.message; - expect(message, 'to equal', 'theErrorMessage'); - } else { - throw new Error('expected example to fail'); + 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: '/Users/alex/Documents/projects/unexpected-markdown', + 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 (:2:1) should succeed', function () { + var snippetIndex = 0; + var evaluatedSnippet = this.evaluatedSnippets.get(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 (:14:1) should succeed with the correct output', function () { + var snippetIndex = 1; + var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + var expectedOutput = 'theErrorMessage'; + expect(expectedOutput, 'to equal', evaluatedSnippet.code); + }); }); } ); @@ -170,76 +169,54 @@ describe('convertMarkdownToMocha', function () { )}`, 'to come out as', function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } + var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); + var globalExpect = global.expect; describe('', function () { - it('example #1 (:2:1) should fail with the correct error message', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var blah = 'abc'; - if (blah === 'abc') { - __returnValue1 = expect.promise(function (resolve, reject) { - setImmediate(resolve); - }); - break example1; - } else { - __returnValue1 = 456; - break example1; - } - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - function cleanStackTrace(stack) { - var lines = stack.split('\n'); - let numStackLines = 0; - for (var i = 0; i < lines.length; i += 1) { - const matchStackLine = lines[i].match( - /^( +at (?:[\w<>]+ \()?)[^)]+(\)?)$/ - ); - if (matchStackLine) { - numStackLines += 1; - if (numStackLines <= 2) { - lines[i] = // eslint-disable-next-line prefer-template - matchStackLine[1] + - '/path/to/file.js:x:y' + - matchStackLine[2]; - } else { - lines.splice(i, 1); - i -= 1; - } - } else { - numStackLines = 0; - } - } - return lines.join('\n'); - } - if (err) { - var message = err.isUnexpected - ? err.getErrorMessage('text').toString() - : err.message; - message = cleanStackTrace(message); - expect(message, 'to equal', 'theErrorMessage'); - } else { - throw new Error('expected example to fail'); + 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\n```output\ntheErrorMessage\n```\n", + { + marker: 'unexpected-markdown', + capture: 'return', + pwdPath: '/Users/alex/Documents/projects/unexpected-markdown', + 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 (:2:1) should succeed', function () { + var snippetIndex = 0; + var evaluatedSnippet = this.evaluatedSnippets.get(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 (:14:1) should succeed with the correct output', function () { + var snippetIndex = 1; + var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + var expectedOutput = 'theErrorMessage'; + expect(expectedOutput, 'to equal', evaluatedSnippet.code); + }); }); } ); @@ -253,275 +230,69 @@ describe('convertMarkdownToMocha', function () { )}\n${fences(synchronousSuccessfulSnippet)}`, 'to come out as', function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } + var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); + var globalExpect = global.expect; describe('', function () { - it('example #1 (:2:1) should fail with the correct error message', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var blah = 'abc'; - if (blah === 'abc') { - __returnValue1 = expect.promise(function (resolve, reject) { - setImmediate(resolve); - }); - break example1; - } else { - __returnValue1 = 456; - break example1; - } - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - var message = err.isUnexpected - ? err.getErrorMessage('text').toString() - : err.message; - expect(message, 'to equal', 'theErrorMessage'); - } else { - throw new Error('expected example to fail'); + 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: '/Users/alex/Documents/projects/unexpected-markdown', + 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 #2 (:18:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var blah = 'abc'; - if (blah === 'abc') { - __returnValue1 = expect.promise(function (resolve, reject) { - setImmediate(resolve); - }); - break example1; - } else { - __returnValue1 = 456; - break example1; - } - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - // eslint-disable-next-line handle-callback-err - function endOfExample1(err) { - var __returnValue2; - example2: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample2(err); - } - if (isPromise(__returnValue2)) { - return __returnValue2.then(function () { - return endOfExample2(); - }, endOfExample2); - } else { - return endOfExample2(); - } - function endOfExample2(err) { - if (err) { - expect.fail(err); - } - } - } - }); - }); - } - ); - }); - - it('should convert non-returning snippet expected to be successful', function () { - expect(fences(synchronousSuccessfulSnippet), 'to come out as', function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } - - describe('', function () { - it('example #1 (:2:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - expect.fail(err); - } - } - }); - }); - }); - }); - - it('should convert a non-returning snippet expected to fail', function () { - expect( - `${fences(synchronousThrowingSnippet)}\n${fences( - 'theErrorMessage', - 'output' - )}`, - 'to come out as', - function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } - - describe('', function () { - it('example #1 (:2:1) should fail with the correct error message', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var bar = 'abc'; - expect(bar, 'to equal', 'def'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - var message = err.isUnexpected - ? err.getErrorMessage('text').toString() - : err.message; - expect(message, 'to equal', 'theErrorMessage'); - } else { - throw new Error('expected example to fail'); - } + it('example #1 (:2:1) should succeed', function () { + var snippetIndex = 0; + var evaluatedSnippet = this.evaluatedSnippets.get(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 non-returning snippet expected to fail followed by another one', function () { - expect( - `${fences(synchronousThrowingSnippet)}\n${fences( - 'theErrorMessage', - 'output' - )}\n${fences(synchronousSuccessfulSnippet)}`, - 'to come out as', - function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } - - describe('', function () { - it('example #1 (:2:1) should fail with the correct error message', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var bar = 'abc'; - expect(bar, 'to equal', 'def'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - var message = err.isUnexpected - ? err.getErrorMessage('text').toString() - : err.message; - expect(message, 'to equal', 'theErrorMessage'); - } else { - throw new Error('expected example to fail'); - } - } + it('example #2 (:14:1) should succeed with the correct output', function () { + var snippetIndex = 1; + var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + var expectedOutput = 'theErrorMessage'; + expect(expectedOutput, 'to equal', evaluatedSnippet.code); }); - it('example #2 (:12:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var bar = 'abc'; - expect(bar, 'to equal', 'def'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - // eslint-disable-next-line handle-callback-err - function endOfExample1(err) { - var __returnValue2; - example2: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample2(err); - } - if (isPromise(__returnValue2)) { - return __returnValue2.then(function () { - return endOfExample2(); - }, endOfExample2); - } else { - return endOfExample2(); - } - function endOfExample2(err) { - if (err) { - expect.fail(err); - } - } + it('example #3 (:18:1) should succeed', function () { + var snippetIndex = 2; + var evaluatedSnippet = this.evaluatedSnippets.get(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}` + ); } }); }); @@ -536,162 +307,62 @@ describe('convertMarkdownToMocha', function () { )}`, 'to come out as', function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } + var { Markdown } = require('./node_modules/evaldown/lib/Evaldown.js'); + var globalExpect = global.expect; describe('', function () { - it('example #1 (:2:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - expect.fail(err); - } - } - }); - - it('example #2 (:8:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - // eslint-disable-next-line handle-callback-err - function endOfExample1(err) { - var __returnValue2; - example2: try { - var bar = 'abc'; - expect(bar, 'to equal', 'def'); - } catch (err) { - return endOfExample2(err); + 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: '/Users/alex/Documents/projects/unexpected-markdown', + fileGlobals: { expect: () => expect.clone() }, } - if (isPromise(__returnValue2)) { - return __returnValue2.then(function () { - return endOfExample2(); - }, endOfExample2); - } else { - return endOfExample2(); - } - function endOfExample2(err) { - if (err) { - expect.fail(err); - } - } - } + ); + await md.evaluate(); + this.evaluatedSnippets = md.getSnippets(); + this.isNextEvaluatedSnippetOutput = (index) => { + var nextSnippet = this.evaluatedSnippets.get(index + 1); + return !!nextSnippet && nextSnippet.lang === 'output'; + }; }); - }); - } - ); - }); - it('should inject a fresh unexpected clone before a snippet with #freshExpect:true', function () { - expect( - `${fences(synchronousSuccessfulSnippet)}\n${fences( - synchronousThrowingSnippet, - 'javascript#freshExpect:true' - )}`, - 'to come out as', - function () { - function isPromise(obj) { - return obj && typeof obj.then === 'function'; - } - - if (typeof unexpected === 'undefined') { - unexpected = require('unexpected'); - unexpected.output.preferredWidth = 80; - } - - describe('', function () { it('example #1 (:2:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - function endOfExample1(err) { - if (err) { - expect.fail(err); - } + var snippetIndex = 0; + var evaluatedSnippet = this.evaluatedSnippets.get(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 (:8:1) should succeed', function () { - var expect = unexpected.clone(); - var __returnValue1; - example1: try { - var foo = 'abc'; - expect(foo, 'to equal', 'abc'); - } catch (err) { - return endOfExample1(err); - } - if (isPromise(__returnValue1)) { - return __returnValue1.then(function () { - return endOfExample1(); - }, endOfExample1); - } else { - return endOfExample1(); - } - // eslint-disable-next-line handle-callback-err - function endOfExample1(err) { - expect = unexpected.clone(); - var __returnValue2; - example2: try { - var bar = 'abc'; - expect(bar, 'to equal', 'def'); - } catch (err) { - return endOfExample2(err); - } - if (isPromise(__returnValue2)) { - return __returnValue2.then(function () { - return endOfExample2(); - }, endOfExample2); - } else { - return endOfExample2(); - } - function endOfExample2(err) { - if (err) { - expect.fail(err); - } - } + var snippetIndex = 1; + var evaluatedSnippet = this.evaluatedSnippets.get(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}` + ); } }); }); From 07e4161327e87c0e0a2c2026cd08cad614072cb0 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sat, 2 May 2020 18:25:24 +0200 Subject: [PATCH 04/10] Repair tests on CI. --- test/convertMarkdownToMocha.spec.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/convertMarkdownToMocha.spec.js b/test/convertMarkdownToMocha.spec.js index 47be998..6a6a627 100644 --- a/test/convertMarkdownToMocha.spec.js +++ b/test/convertMarkdownToMocha.spec.js @@ -4,6 +4,8 @@ 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') { @@ -29,7 +31,7 @@ var expect = require('unexpected') '}' ), 'to equal', - codeToString(value) + codeToString(value).replace(//g, projectPath) ); }); @@ -68,7 +70,7 @@ describe('convertMarkdownToMocha', function () { { marker: 'unexpected-markdown', capture: 'return', - pwdPath: '/Users/alex/Documents/projects/unexpected-markdown', + pwdPath: '', fileGlobals: { expect: () => expect.clone() }, } ); @@ -118,7 +120,7 @@ describe('convertMarkdownToMocha', function () { { marker: 'unexpected-markdown', capture: 'return', - pwdPath: '/Users/alex/Documents/projects/unexpected-markdown', + pwdPath: '', fileGlobals: { expect: () => expect.clone() }, } ); @@ -182,7 +184,7 @@ describe('convertMarkdownToMocha', function () { { marker: 'unexpected-markdown', capture: 'return', - pwdPath: '/Users/alex/Documents/projects/unexpected-markdown', + pwdPath: '', fileGlobals: { expect: () => expect.clone() }, } ); @@ -243,7 +245,7 @@ describe('convertMarkdownToMocha', function () { { marker: 'unexpected-markdown', capture: 'return', - pwdPath: '/Users/alex/Documents/projects/unexpected-markdown', + pwdPath: '', fileGlobals: { expect: () => expect.clone() }, } ); @@ -320,7 +322,7 @@ describe('convertMarkdownToMocha', function () { { marker: 'unexpected-markdown', capture: 'return', - pwdPath: '/Users/alex/Documents/projects/unexpected-markdown', + pwdPath: '', fileGlobals: { expect: () => expect.clone() }, } ); From f6d3a36ae1ddb79b6ad8ac986575eea77a6370e2 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sat, 2 May 2020 18:40:51 +0200 Subject: [PATCH 05/10] Repair coverage. --- lib/convertMarkdownToMocha.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/convertMarkdownToMocha.js b/lib/convertMarkdownToMocha.js index b3d4fa2..b9c8a0d 100644 --- a/lib/convertMarkdownToMocha.js +++ b/lib/convertMarkdownToMocha.js @@ -152,6 +152,7 @@ module.exports = function (mdSrc, filePath) { // add deferencing the corresponding evaluated snippet /* eslint-disable no-unused-vars */ + /* istanbul ignore next */ var itSnippetStatements = parseFunctionBody(function f() { var snippetIndex = 0; var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); @@ -167,6 +168,7 @@ module.exports = function (mdSrc, filePath) { // assert the output in the markdown file is the same as the evaluated output /* eslint-disable no-undef */ + /* istanbul ignore next */ var itOutputStatements = parseFunctionBody(function f() { var expectedOutput = ''; expect(expectedOutput, 'to equal', evaluatedSnippet.code); @@ -181,6 +183,7 @@ module.exports = function (mdSrc, filePath) { // assert that the snippet executed correctly /* eslint-disable no-undef */ + /* istanbul ignore next */ var itEvaluatedSnippets = parseFunctionBody(function f() { var { output } = evaluatedSnippet; From 39e2095dddc37f0f42f2eae185dd4e6d98c6a3f6 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Fri, 8 May 2020 14:29:34 +0200 Subject: [PATCH 06/10] Fix checking actual output against expected output and cover with a test. --- lib/convertMarkdownToMocha.js | 9 ++- test/convertMarkdownToMocha.spec.js | 114 ++++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/lib/convertMarkdownToMocha.js b/lib/convertMarkdownToMocha.js index b9c8a0d..d81ac56 100644 --- a/lib/convertMarkdownToMocha.js +++ b/lib/convertMarkdownToMocha.js @@ -155,7 +155,10 @@ module.exports = function (mdSrc, filePath) { /* istanbul ignore next */ var itSnippetStatements = parseFunctionBody(function f() { var snippetIndex = 0; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + var currentSnippet = this.evaluatedSnippets.get(snippetIndex); + var evaluatedSnippet = this.evaluatedSnippets.get( + currentSnippet.lang === 'output' ? snippetIndex - 1 : snippetIndex + ); }); /* eslint-enable no-unused-vars */ itSnippetStatements[0].declarations[0].init.value = i; @@ -170,8 +173,8 @@ module.exports = function (mdSrc, filePath) { /* eslint-disable no-undef */ /* istanbul ignore next */ var itOutputStatements = parseFunctionBody(function f() { - var expectedOutput = ''; - expect(expectedOutput, 'to equal', evaluatedSnippet.code); + var writtenOutput = ''; + expect(evaluatedSnippet.output.text, 'to equal', writtenOutput); }); /* eslint-enable no-undef */ itOutputStatements[0].declarations[0].init.value = codeBlock.code; diff --git a/test/convertMarkdownToMocha.spec.js b/test/convertMarkdownToMocha.spec.js index 6a6a627..67e85aa 100644 --- a/test/convertMarkdownToMocha.spec.js +++ b/test/convertMarkdownToMocha.spec.js @@ -1,4 +1,7 @@ /* 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'); @@ -17,6 +20,40 @@ function codeToString(obj) { 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')) @@ -83,7 +120,10 @@ describe('convertMarkdownToMocha', function () { }); it('example #1 (:2:1) should succeed', function () { var snippetIndex = 0; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -133,7 +173,10 @@ describe('convertMarkdownToMocha', function () { }); it('example #1 (:2:1) should succeed', function () { var snippetIndex = 0; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -150,9 +193,12 @@ describe('convertMarkdownToMocha', function () { it('example #2 (:14:1) should succeed with the correct output', function () { var snippetIndex = 1; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); - var expectedOutput = 'theErrorMessage'; - expect(expectedOutput, 'to equal', evaluatedSnippet.code); + 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); }); }); } @@ -198,7 +244,10 @@ describe('convertMarkdownToMocha', function () { it('example #1 (:2:1) should succeed', function () { var snippetIndex = 0; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -215,9 +264,12 @@ describe('convertMarkdownToMocha', function () { it('example #2 (:14:1) should succeed with the correct output', function () { var snippetIndex = 1; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); - var expectedOutput = 'theErrorMessage'; - expect(expectedOutput, 'to equal', evaluatedSnippet.code); + 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); }); }); } @@ -259,7 +311,10 @@ describe('convertMarkdownToMocha', function () { it('example #1 (:2:1) should succeed', function () { var snippetIndex = 0; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -276,14 +331,20 @@ describe('convertMarkdownToMocha', function () { it('example #2 (:14:1) should succeed with the correct output', function () { var snippetIndex = 1; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); - var expectedOutput = 'theErrorMessage'; - expect(expectedOutput, 'to equal', evaluatedSnippet.code); + 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 (:18:1) should succeed', function () { var snippetIndex = 2; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -336,7 +397,10 @@ describe('convertMarkdownToMocha', function () { it('example #1 (:2:1) should succeed', function () { var snippetIndex = 0; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -353,7 +417,10 @@ describe('convertMarkdownToMocha', function () { it('example #2 (:8:1) should succeed', function () { var snippetIndex = 1; - var evaluatedSnippet = this.evaluatedSnippets.get(snippetIndex); + 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'); @@ -371,4 +438,19 @@ describe('convertMarkdownToMocha', function () { } ); }); + + 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" + ); + }); + }); }); From 0a0628c0fe5d4797abc6dd3aba045c9c48b41be2 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sat, 27 Jun 2020 14:56:21 +0200 Subject: [PATCH 07/10] Bump to the current version of evaldown. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89114d7..356814a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "escodegen": "^1.11.0", "esprima": "^4.0.1", - "evaldown": "^0.6.0", + "evaldown": "0.7.0", "magicpen-prism": "^4.0.0", "marked-papandreou": "^0.3.3-patch3", "source-map": "^0.5.1", From 2073a4e0326f1de6652277730b7d4a10a4f96475 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sat, 27 Jun 2020 22:25:11 +0200 Subject: [PATCH 08/10] Bump to the current version of evaldown. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 356814a..42e343c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "escodegen": "^1.11.0", "esprima": "^4.0.1", - "evaldown": "0.7.0", + "evaldown": "0.7.1", "magicpen-prism": "^4.0.0", "marked-papandreou": "^0.3.3-patch3", "source-map": "^0.5.1", From 94b9be08824a84a72189350e298a5205ae2af9be Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sun, 28 Jun 2020 14:31:57 +0200 Subject: [PATCH 09/10] Use positive logic for code block skip test. --- lib/convertMarkdownToMocha.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/convertMarkdownToMocha.js b/lib/convertMarkdownToMocha.js index d81ac56..9f2629c 100644 --- a/lib/convertMarkdownToMocha.js +++ b/lib/convertMarkdownToMocha.js @@ -5,16 +5,10 @@ var { Markdown } = require('evaldown'); var resolvedPathToEvaldown = require.resolve('evaldown'); -function checkCodeBlockSkipped(codeBlock) { +function isCodeBlockThatShouldBeRun(codeBlock) { const { lang, flags } = codeBlock; - if (!(lang === 'javascript' || lang === 'output')) { - return true; - } else if (lang === 'javascript' && !flags.evaluate) { - return true; - } - - return false; + return lang === 'output' || (lang === 'javascript' && flags.evaluate); } function parseFunctionBody(fn) { @@ -113,9 +107,9 @@ module.exports = function (mdSrc, filePath) { codeBlock.lineNumber + 1 }:1)`; - let isSkipped = false; - let itExpressionStatement; - if ((isSkipped = checkCodeBlockSkipped(codeBlock))) { + var isSkipped = false; + var itExpressionStatement; + if ((isSkipped = !isCodeBlockThatShouldBeRun(codeBlock))) { /* eslint-disable */ itExpressionStatement = parseFunctionBody( /* istanbul ignore next */ From cf67f05e3ba678f4b99d729b03b550372dadadb3 Mon Sep 17 00:00:00 2001 From: Alex J Burke Date: Sat, 4 Jul 2020 16:13:55 +0200 Subject: [PATCH 10/10] Bump evaldown. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 42e343c..7950dcb 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "escodegen": "^1.11.0", "esprima": "^4.0.1", - "evaldown": "0.7.1", + "evaldown": "^1.0.0", "magicpen-prism": "^4.0.0", "marked-papandreou": "^0.3.3-patch3", "source-map": "^0.5.1",