diff --git a/lib/Comparison.js b/lib/Comparison.js new file mode 100644 index 00000000..87bcb9b9 --- /dev/null +++ b/lib/Comparison.js @@ -0,0 +1,28 @@ +// Incorrect :: Array Effect -> Array Effect -> Comparison +const Incorrect = actual => expected => ({ + tag: 'Incorrect', + actual, + expected, +}); + +// Correct :: Array Effect -> Comparison +const Correct = actual => ({ + tag: 'Correct', + actual, +}); + +// comparison :: (Array Effect -> Array Effect -> a) +// -> (Array Effect -> a) +// -> Comparison +// -> a +const comparison = incorrect => correct => comparison => { + switch (comparison.tag) { + case 'Incorrect': + return incorrect (comparison.actual) (comparison.expected); + case 'Correct': + return correct (comparison.actual); + } +}; + + +export {Incorrect, Correct, comparison}; diff --git a/lib/Effect.js b/lib/Effect.js new file mode 100644 index 00000000..86c8fa98 --- /dev/null +++ b/lib/Effect.js @@ -0,0 +1,31 @@ +// Failure :: Any -> Effect +const Failure = exception => ({ + tag: 'Failure', + exception, +}); + +// Success :: Any -> Effect +const Success = value => ({ + tag: 'Success', + value, +}); + +// effect :: (Any -> a) -> (Any -> a) -> Effect -> a +const effect = failure => success => effect => { + switch (effect.tag) { + case 'Failure': return failure (effect.exception); + case 'Success': return success (effect.value); + } +}; + +// encase :: AnyFunction -> ...Any -> Effect +const encase = f => (...args) => { + try { + return Success (f (...args)); + } catch (exception) { + return Failure (exception); + } +}; + + +export {Failure, Success, effect, encase}; diff --git a/lib/Line.js b/lib/Line.js new file mode 100644 index 00000000..3e5f6b0a --- /dev/null +++ b/lib/Line.js @@ -0,0 +1,5 @@ +// Line :: Integer -> String -> Line +const Line = number => text => ({number, text}); + + +export {Line}; diff --git a/lib/command.js b/lib/command.js index 07f024eb..0b6008c8 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,3 +1,4 @@ +import {comparison} from './Comparison.js'; import doctest from './doctest.js'; import program from './program.js'; @@ -5,9 +6,12 @@ import program from './program.js'; Promise.all ( program.args.map (path => doctest (program) (path) - .then (results => - results.reduce ((status, [correct]) => correct ? status : 1, 0) - ) + .then (tests => tests.reduce ( + (status, test) => comparison (_ => _ => 1) + (_ => status) + (test.comparison), + 0 + )) ) ) .then ( diff --git a/lib/doctest.js b/lib/doctest.js index d9482bfd..e84facff 100644 --- a/lib/doctest.js +++ b/lib/doctest.js @@ -13,9 +13,12 @@ import {resolve} from 'path'; import * as acorn from 'acorn'; import CoffeeScript from 'coffeescript'; -import _show from 'sanctuary-show'; +import show from 'sanctuary-show'; import Z from 'sanctuary-type-classes'; +import {Incorrect, Correct, comparison} from './Comparison.js'; +import {Failure, Success, effect, encase} from './Effect.js'; +import {Line} from './Line.js'; import require from './require.js'; @@ -25,54 +28,83 @@ const compose = f => g => x => f (g (x)); // indentN :: Integer -> String -> String const indentN = n => s => s.replace (/^(?!$)/gm, ' '.repeat (n)); -// quote :: String -> String -const quote = s => `'${s.replace (/'/g, "\\'")}'`; +// formatLines :: Integer -> NonEmpty (Array Line) -> String +const formatLines = indent => lines => ( + lines + .map (line => `{number: ${show (line.number)}, text: ${show (line.text)}},`) + .join ('\n' + ' '.repeat (indent)) +); + +// formatInput :: Integer -> NonEmpty (Array Line) -> String +const formatInput = indent => lines => ( + lines + .map (line => line.text.replace (/^\s*([>]|[.]+)[ ]?/, '')) + .join ('\n' + ' '.repeat (indent)) +); + +// formatOutput :: Integer -> NonEmpty (Array Line) -> String +const formatOutput = indent => lines => { + const [head, ...tail] = lines.map (line => line.text.replace (/^\s*/, '')); + const match = /^![ ]?([^:]*)(?::[ ]?(.*))?$/.exec (head); + return [ + `${head.startsWith ('!') ? 'throw' : 'return'} (`, + ` ${match == null ? head : `new ${match[1]}(${show (match[2] ?? '')})`}`, + ...(tail.map (text => ' ' + text.replace (/^[.]+[ ]?/, ''))), + ')', + ].join ('\n' + ' '.repeat (indent)); +}; const wrapJs = sourceType => test => { - const source = test.input.value; + const source = formatInput (0) (test.input.lines); const ast = acorn.parse ( source.startsWith ('{') && source.endsWith ('}') ? `(${source})` : source, {ecmaVersion: 2020, sourceType} ); const {type} = ast.body[0]; - return type === 'FunctionDeclaration' || - type === 'ImportDeclaration' || - type === 'VariableDeclaration' ? - test.input.value : - ` -__doctest.enqueue({ - type: "input", - thunk: () => { - return ${test.input.value}; - }, -}); -${test.output == null ? '' : ` + if (type !== 'ExpressionStatement') return source; + + return ` __doctest.enqueue({ - type: "output", - ":": ${test.output.loc.start.line}, - "!": ${test['!']}, - thunk: () => { - return ${test.output.value}; + input: { + lines: [ + ${formatLines (6) (test.input.lines)} + ], + thunk: () => { + return ( + ${formatInput (8) (test.input.lines)} + ); + }, }, + output: ${test.output && `{ + lines: [ + ${formatLines (6) (test.output.lines)} + ], + thunk: () => { + ${formatOutput (6) (test.output.lines)}; + }, + }`}, }); -`}`; +`; }; const wrapCoffee = test => ` __doctest.enqueue { - type: "input" - thunk: -> -${indentN (4) (test.input.value)} -} -${test.output == null ? '' : ` -__doctest.enqueue { - type: "output" - ":": ${test.output.loc.start.line} - "!": ${test['!']} - thunk: -> -${indentN (4) (test.output.value)} + input: { + lines: [ + ${formatLines (6) (test.input.lines)} + ] + thunk: -> + ${formatInput (6) (test.input.lines)} + } + output: ${test.output && `{ + lines: [ + ${formatLines (6) (test.output.lines)} + ] + thunk: -> + ${formatOutput (6) (test.output.lines)} + }`} } -`}`; +`; const wrapModule = source => ` export const __doctest = { @@ -83,18 +115,6 @@ export const __doctest = { ${source} `; -// normalizeTest :: { output :: { value :: String } } -> Undefined -const normalizeTest = $test => { - const $output = $test.output; - if ($output != null) { - const match = $output.value.match (/^![ ]?([^:]*)(?::[ ]?(.*))?$/); - $test['!'] = match != null; - if ($test['!']) { - $output.value = `new ${match[1]}(${quote (match[2] ?? '')})`; - } - } -}; - // Location = { start :: { line :: Integer, column :: Integer } // , end :: { line :: Integer, column :: Integer } } @@ -104,9 +124,8 @@ const normalizeTest = $test => { // , closingDelimiter :: String? } // -> Array { type :: String, value :: String, loc :: Location } // -> Array { commentIndex :: Integer -// , ! :: Boolean -// , input :: { value :: String, loc :: Location } -// , output :: { value :: String, loc :: Location } } +// , input :: { lines :: Array Line, loc :: Location } +// , output :: { lines :: Array Line, loc :: Location } } // // Returns the doctests present in the given esprima comment objects. // @@ -119,11 +138,10 @@ const normalizeTest = $test => { // . value: ' 42', // . loc: {start: {line: 2, column: 0}, end: {line: 2, column: 5}}}]) // [{commentIndex: 1, -// . '!': false, -// . input: {value: '6 * 7', +// . input: {lines: [Line (1) ('> 6 * 7')], // . loc: {start: {line: 1, column: 0}, // . end: {line: 1, column: 10}}}, -// . output: {value: '42', +// . output: {lines: [Line (2) ('42')], // . loc: {start: {line: 2, column: 0}, // . end: {line: 2, column: 5}}}}] const transformComments = ({ @@ -134,19 +152,20 @@ const transformComments = ({ const result = comments.reduce ( (accum, comment, commentIndex) => comment.value.split ('\n') - .reduce ((accum, line, idx) => { + .reduce ((accum, text, idx) => { let uncommented, start, end; if (comment.type === 'Block') { - uncommented = line.replace (/^\s*[*]?\s*/, ''); + uncommented = text.replace (/^\s*[*]/, ''); start = end = {line: comment.loc.start.line + idx}; } else if (comment.type === 'Line') { - uncommented = line.replace (/^\s*/, ''); + uncommented = text; ({start, end} = comment.loc); } if (uncommented.startsWith (prefix)) { const unprefixed = uncommented .slice (prefix.length) .replace (/^\s*/, ''); + const line = Line (start.line) (unprefixed); if (accum.state === 'closed') { if (unprefixed === openingDelimiter) accum.state = 'open'; } else if (unprefixed === closingDelimiter) { @@ -154,18 +173,17 @@ const transformComments = ({ } else if (unprefixed.startsWith ('>')) { accum.tests.push ({ [accum.state = 'input']: { - value: unprefixed.replace (/^[>][ ]?/, ''), + lines: [line], loc: {start, end}, }, }); } else if (unprefixed.startsWith ('.')) { + accum.tests[accum.tests.length - 1][accum.state].lines.push (line); accum.tests[accum.tests.length - 1][accum.state].loc.end = end; - accum.tests[accum.tests.length - 1][accum.state].value += - '\n' + unprefixed.replace (/^[.]+[ ]?/, ''); } else if (accum.state === 'input') { accum.tests[accum.tests.length - 1].commentIndex = commentIndex; accum.tests[accum.tests.length - 1][accum.state = 'output'] = { - value: unprefixed, + lines: [line], loc: {start, end}, }; } @@ -175,9 +193,7 @@ const transformComments = ({ {state: openingDelimiter == null ? 'open' : 'closed', tests: []} ); - const $tests = result.tests; - $tests.forEach (normalizeTest); - return $tests; + return result.tests; }; // substring @@ -225,7 +241,6 @@ const rewriteJs = sourceType => ({ // to be captured: const bookend = { - value: '', loc: {start: {line: Infinity, column: Infinity}}, }; @@ -308,29 +323,31 @@ const rewriteCoffee = ({ closingDelimiter, }) => input => { const lines = input.match (/^.*(?=\n)/gm); - const chunks = lines.reduce ((accum, line, idx) => { - const isComment = /^[ \t]*#(?!##)/.test (line); + const chunks = lines.reduce ((accum, text, idx) => { + const isComment = /^[ \t]*#(?!##)/.test (text); const current = isComment ? accum.commentChunks : accum.literalChunks; + const line = Line (idx + 1) (text); if (isComment === accum.isComment) { - current[current.length - 1].lines.push (line); + current[current.length - 1].push (line); } else { - current.push ({lines: [line], loc: {start: {line: idx + 1}}}); + current.push ([line]); } accum.isComment = isComment; return accum; }, { - literalChunks: [{lines: [], loc: {start: {line: 1}}}], + literalChunks: [[]], commentChunks: [], isComment: false, }); const testChunks = chunks.commentChunks.map (commentChunk => { - const result = commentChunk.lines.reduce ((accum, line, idx) => { - const [, indent, uncommented] = line.match (/^([ \t]*)#[ \t]*(.*)$/); + const result = commentChunk.reduce ((accum, {number, text}) => { + const [, indent, uncommented] = text.match (/^([ \t]*)#(.*)$/); if (uncommented.startsWith (prefix)) { const unprefixed = uncommented .slice (prefix.length) .replace (/^\s*/, ''); + const line = Line (number) (unprefixed); if (accum.state === 'closed') { if (unprefixed === openingDelimiter) accum.state = 'open'; } else if (unprefixed === closingDelimiter) { @@ -339,33 +356,32 @@ const rewriteCoffee = ({ accum.tests.push ({ indent, [accum.state = 'input']: { - value: unprefixed.replace (/^[>][ ]?/, ''), + lines: [line], }, }); } else if (unprefixed.startsWith ('.')) { - accum.tests[accum.tests.length - 1][accum.state].value += - '\n' + unprefixed.replace (/^[.]+[ ]?/, ''); + accum.tests[accum.tests.length - 1][accum.state].lines.push ( + line + ); } else if (accum.state === 'input') { accum.tests[accum.tests.length - 1][accum.state = 'output'] = { - value: unprefixed, - loc: {start: {line: commentChunk.loc.start.line + idx}}, + lines: [line], }; } } return accum; }, {state: openingDelimiter == null ? 'open' : 'closed', tests: []}); - return result.tests.map ($test => { - normalizeTest ($test); - return indentN ($test.indent.length) (wrapCoffee ($test)); - }); + return result.tests.map ( + test => indentN (test.indent.length) (wrapCoffee (test)) + ); }); return CoffeeScript.compile ( chunks.literalChunks.reduce ( (s, chunk, idx) => Z.reduce ( (s, line) => `${s}${line}\n`, - chunk.lines.reduce ((s, line) => `${s}${line}\n`, s), + chunk.reduce ((s, line) => `${s}${line.text}\n`, s), idx < testChunks.length ? testChunks[idx] : [] ), '' @@ -396,49 +412,26 @@ ${arrowWrap (source)} } }; -// show :: a -> String -const show = x => - Object.prototype.toString.call (x) === '[object Error]' ? - String (x) : - _show (x); - const run = queue => - queue.reduce ((accum, io) => { - const {thunk} = accum; - if (io.type === 'input') { - if (thunk != null) thunk (); - accum.thunk = io.thunk; - } else if (io.type === 'output') { - let either; - try { - either = {tag: 'Right', value: thunk ()}; - } catch (err) { - either = {tag: 'Left', value: err}; - } - accum.thunk = null; - const expected = io.thunk (); - - let pass, repr; - if (either.tag === 'Left') { - const {name, message} = either.value; - pass = io['!'] && - name === expected.name && - message === expected.message.replace (/^$/, message); - repr = `! ${expected.message === '' ? name : either.value}`; - } else { - pass = !io['!'] && Z.equals (either.value, expected); - repr = show (either.value); - } - - accum.results.push ([ - pass, - repr, - io['!'] ? `! ${expected}` : show (expected), - io[':'], - ]); - } - return accum; - }, {results: [], thunk: null}).results; + queue.flatMap (({input, output}) => { + const i = encase (input.thunk) (); + if (output == null) return []; + const o = encase (output.thunk) (); + const comparison = ( + effect (o => effect (i => i.name === o.name && + i.message === (o.message || i.message) ? + Correct (Failure (i)) : + Incorrect (Failure (i)) (Failure (o))) + (i => Incorrect (Success (i)) (Failure (o)))) + (o => effect (i => Incorrect (Failure (i)) (Success (o))) + (i => Z.equals (i, o) ? + Correct (Success (i)) : + Incorrect (Success (i)) (Success (o)))) + (o) + (i) + ); + return [{lines: {input: input.lines, output: output.lines}, comparison}]; + }); const evaluateModule = path => source => { const abspath = resolve (path) @@ -483,17 +476,26 @@ const functionEval = async source => { /* c8 ignore next */ }; -const log = results => { +const log = tests => { console.log ( - results.reduce ((s, [correct]) => `${s}${correct ? '.' : 'x'}`, '') + tests + .map (test => comparison (_ => _ => 'x') (_ => '.') (test.comparison)) + .join ('') ); - results.forEach (([correct, actual, expected, line]) => { - if (!correct) { - console.log ( - `FAIL: expected ${expected} on line ${line} (got ${actual})` - ); - } - }); + for (const test of tests) { + comparison + (actual => expected => { + console.log (`FAIL: expected ${ + effect (x => `! ${x}`) (show) (expected) + } on line ${ + test.lines.output[test.lines.output.length - 1].number + } (got ${ + effect (x => `! ${x}`) (show) (actual) + })`); + }) + (_ => {}) + (test.comparison); + } }; const test = options => path => rewrite => evaluate => { diff --git a/test/bin/results.js b/test/bin/results.js index fb8632ff..59b5892a 100644 --- a/test/bin/results.js +++ b/test/bin/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'executable without file extension', - [ - true, - '42', - '42', - 4, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('executable without file extension') + ([Line (3) ('> identity(42)')]) + ([Line (4) ('42')]) + (Correct (Success (42))), + ]; diff --git a/test/commonjs/__dirname/results.js b/test/commonjs/__dirname/results.js index 106c0325..3d45c1c1 100644 --- a/test/commonjs/__dirname/results.js +++ b/test/commonjs/__dirname/results.js @@ -1,11 +1,8 @@ -export default [ - [ - '__dirname is defined', - [ - true, - '"string"', - '"string"', - 4, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('__dirname is defined') + ([Line (3) ('> typeof __dirname')]) + ([Line (4) ("'string'")]) + (Correct (Success ('string'))), + ]; diff --git a/test/commonjs/__doctest.require/results.js b/test/commonjs/__doctest.require/results.js index ba4ec070..2c3f78b3 100644 --- a/test/commonjs/__doctest.require/results.js +++ b/test/commonjs/__doctest.require/results.js @@ -1,11 +1,8 @@ -export default [ - [ - '__doctest.require', - [ - true, - '"sanctuary.js.org"', - '"sanctuary.js.org"', - 6, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('__doctest.require') + ([Line (5) ("> (new url.URL ('https://sanctuary.js.org/')).hostname")]) + ([Line (6) ("'sanctuary.js.org'")]) + (Correct (Success ('sanctuary.js.org'))), + ]; diff --git a/test/commonjs/__filename/results.js b/test/commonjs/__filename/results.js index d689f845..7df52365 100644 --- a/test/commonjs/__filename/results.js +++ b/test/commonjs/__filename/results.js @@ -1,11 +1,8 @@ -export default [ - [ - '__filename is defined', - [ - true, - '"string"', - '"string"', - 4, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('__filename is defined') + ([Line (3) ('> typeof __filename')]) + ([Line (4) ("'string'")]) + (Correct (Success ('string'))), + ]; diff --git a/test/commonjs/exports/results.js b/test/commonjs/exports/results.js index cd551c61..eba1f37d 100644 --- a/test/commonjs/exports/results.js +++ b/test/commonjs/exports/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'exports', - [ - true, - '42', - '42', - 2, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('exports') + ([Line (1) ('> exports.identity(42)')]) + ([Line (2) ('42')]) + (Correct (Success (42))), + ]; diff --git a/test/commonjs/module.exports/results.js b/test/commonjs/module.exports/results.js index de6a7acc..c06f84b3 100644 --- a/test/commonjs/module.exports/results.js +++ b/test/commonjs/module.exports/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'module.exports', - [ - true, - '42', - '42', - 2, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('module.exports') + ([Line (1) ('> module.exports(42)')]) + ([Line (2) ('42')]) + (Correct (Success (42))), + ]; diff --git a/test/commonjs/require/results.js b/test/commonjs/require/results.js index cbec56a8..a0085fa6 100644 --- a/test/commonjs/require/results.js +++ b/test/commonjs/require/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'require another CommonJS module', - [ - true, - '"function"', - '"function"', - 2, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('require another CommonJS module') + ([Line (1) ('> typeof $require("assert")')]) + ([Line (2) ('"function"')]) + (Correct (Success ('function'))), + ]; diff --git a/test/commonjs/strict/results.js b/test/commonjs/strict/results.js index d6d30663..74778917 100644 --- a/test/commonjs/strict/results.js +++ b/test/commonjs/strict/results.js @@ -1,11 +1,8 @@ -export default [ - [ - "preserves 'use strict' directive", - [ - true, - 'undefined', - 'undefined', - 4, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ("preserves 'use strict' directive") + ([Line (3) ('> (function() { return this; }())')]) + ([Line (4) ('undefined')]) + (Correct (Success (undefined))), + ]; diff --git a/test/es2015/results.js b/test/es2015/results.js index 285b80e9..1f3cb572 100644 --- a/test/es2015/results.js +++ b/test/es2015/results.js @@ -1,7 +1,28 @@ -export default [ - ['seq.next().value', [true, '1', '1', 13]], - ['seq.next().value', [true, '1', '1', 15]], - ['seq.next().value', [true, '2', '2', 17]], - ['seq.next().value', [true, '3', '3', 19]], - ['seq.next().value', [true, '5', '5', 21]], +export default ({Test, Line, Correct, Success}) => [ + + Test ('seq.next().value') + ([Line (12) ('> seq.next().value')]) + ([Line (13) ('1')]) + (Correct (Success (1))), + + Test ('seq.next().value') + ([Line (14) ('> seq.next().value')]) + ([Line (15) ('1')]) + (Correct (Success (1))), + + Test ('seq.next().value') + ([Line (16) ('> seq.next().value')]) + ([Line (17) ('2')]) + (Correct (Success (2))), + + Test ('seq.next().value') + ([Line (18) ('> seq.next().value')]) + ([Line (19) ('3')]) + (Correct (Success (3))), + + Test ('seq.next().value') + ([Line (20) ('> seq.next().value')]) + ([Line (21) ('5')]) + (Correct (Success (5))), + ]; diff --git a/test/es2018/results.js b/test/es2018/results.js index ff73f021..990762fd 100644 --- a/test/es2018/results.js +++ b/test/es2018/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'object spread syntax', - [ - true, - '{"x": 1, "y": 2, "z": 4}', - '{"x": 1, "y": 2, "z": 4}', - 2, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('object spread syntax') + ([Line (1) ('> {x: 0, ...{x: 1, y: 2, z: 3}, z: 4}')]) + ([Line (2) ('{x: 1, y: 2, z: 4}')]) + (Correct (Success ({x: 1, y: 2, z: 4}))), + ]; diff --git a/test/es2020/results.js b/test/es2020/results.js index 67b438e0..9bde95cd 100644 --- a/test/es2020/results.js +++ b/test/es2020/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'nullish coalescing operator', - [ - true, - '"default"', - '"default"', - 2, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('nullish coalescing operator') + ([Line (1) ("> null ?? 'default'")]) + ([Line (2) ("'default'")]) + (Correct (Success ('default'))), + ]; diff --git a/test/exceptions/index.js b/test/exceptions/index.js index cc6ab866..d56a5f8c 100644 --- a/test/exceptions/index.js +++ b/test/exceptions/index.js @@ -29,6 +29,9 @@ // > sqrt(-1) // ! Error: XXX +// > 'foo' + 'bar' +// foobar + var sqrt = function(n) { if (n >= 0) { return Math.sqrt(n); diff --git a/test/exceptions/results.js b/test/exceptions/results.js index 366ef25c..e073d863 100644 --- a/test/exceptions/results.js +++ b/test/exceptions/results.js @@ -1,92 +1,66 @@ -export default [ - [ - 'input evaluates to Error with expected message', - [ - true, - 'Error: Invalid value', - 'Error: Invalid value', - 3, - ], - ], - [ - 'input evaluates to Error without expected message', - [ - false, - 'Error', - 'Error: Invalid value', - 6, - ], - ], - [ - 'input evaluates to Error with unexpected message', - [ - false, - 'Error: Invalid value', - 'Error', - 9, - ], - ], - [ - 'input evaluates to Error with unexpected message', - [ - false, - 'Error: Invalid value', - 'Error: XXX', - 12, - ], - ], - [ - 'evaluating input does not throw expected exception', - [ - false, - 'Error: Invalid value', - '! Error: Invalid value', - 15, - ], - ], - [ - 'evaluating input throws unexpected exception', - [ - false, - '! Error: Invalid value', - 'Error: Invalid value', - 18, - ], - ], - [ - 'evaluating input throws exception as expected, of expected type', - [ - true, - '! RangeError', - '! RangeError', - 21, - ], - ], - [ - 'evaluating input throws exception as expected, of unexpected type', - [ - false, - '! RangeError', - '! Error', - 24, - ], - ], - [ - 'evaluating input throws exception as expected, with expected message', - [ - true, - '! Error: Invalid value', - '! Error: Invalid value', - 27, - ], - ], - [ - 'evaluating input throws exception as expected, with unexpected message', - [ - false, - '! Error: Invalid value', - '! Error: XXX', - 30, - ], - ], +export default ({Test, Line, Incorrect, Correct, Failure, Success}) => [ + + Test ('input evaluates to Error with expected message') + ([Line (2) ("> new Error('Invalid value')")]) + ([Line (3) ("new Error('Invalid value')")]) + (Correct (Success (new Error ('Invalid value')))), + + Test ('input evaluates to Error without expected message') + ([Line (5) ('> new Error()')]) + ([Line (6) ("new Error('Invalid value')")]) + (Incorrect (Success (new Error ())) + (Success (new Error ('Invalid value')))), + + Test ('input evaluates to Error with unexpected message') + ([Line (8) ("> new Error('Invalid value')")]) + ([Line (9) ('new Error()')]) + (Incorrect (Success (new Error ('Invalid value'))) + (Success (new Error ()))), + + Test ('input evaluates to Error with unexpected message') + ([Line (11) ("> new Error('Invalid value')")]) + ([Line (12) ("new Error('XXX')")]) + (Incorrect (Success (new Error ('Invalid value'))) + (Success (new Error ('XXX')))), + + Test ('evaluating input does not throw expected exception') + ([Line (14) ("> new Error('Invalid value')")]) + ([Line (15) ('! Error: Invalid value')]) + (Incorrect (Success (new Error ('Invalid value'))) + (Failure (new Error ('Invalid value')))), + + Test ('evaluating input throws unexpected exception') + ([Line (17) ('> sqrt(-1)')]) + ([Line (18) ("new Error('Invalid value')")]) + (Incorrect (Failure (new Error ('Invalid value'))) + (Success (new Error ('Invalid value')))), + + Test ('evaluating input throws exception as expected, of expected type') + ([Line (20) ('> 0..toString(1)')]) + ([Line (21) ('! RangeError')]) + (Correct (Failure (new RangeError ('toString() radix argument must be between 2 and 36')))), + + Test ('evaluating input throws exception as expected, of unexpected type') + ([Line (23) ('> 0..toString(1)')]) + ([Line (24) ('! Error')]) + (Incorrect (Failure (new RangeError ('toString() radix argument must be between 2 and 36'))) + (Failure (new Error ()))), + + Test ('evaluating input throws exception as expected, with expected message') + ([Line (26) ('> sqrt(-1)')]) + ([Line (27) ('! Error: Invalid value')]) + (Correct (Failure (new Error ('Invalid value')))), + + Test ('evaluating input throws exception as expected, with unexpected message') + ([Line (29) ('> sqrt(-1)')]) + ([Line (30) ('! Error: XXX')]) + (Incorrect (Failure (new Error ('Invalid value'))) + (Failure (new Error ('XXX')))), + + Test ('evaluating output throws unexpected exception') + ([Line (32) ("> 'foo' + 'bar'")]) + ([Line (33) ('foobar')]) + (Incorrect (Success ('foobar')) + (Failure (new ReferenceError ('foobar is not defined')))), + ]; diff --git a/test/fantasy-land/index.js b/test/fantasy-land/index.js index 491ab1e5..da90b034 100644 --- a/test/fantasy-land/index.js +++ b/test/fantasy-land/index.js @@ -14,3 +14,5 @@ Absolute.prototype['@@show'] = function() { Absolute.prototype['fantasy-land/equals'] = function(other) { return Math.abs(this.value) === Math.abs(other.value); }; + +export default Absolute; diff --git a/test/fantasy-land/results.js b/test/fantasy-land/results.js index 920bebe0..e2dc03c9 100644 --- a/test/fantasy-land/results.js +++ b/test/fantasy-land/results.js @@ -1,11 +1,11 @@ -export default [ - [ - 'uses Z.equals for equality checks', - [ - true, - 'Absolute (-1)', - 'Absolute (1)', - 2, - ], - ], +import Absolute from './index.js'; + + +export default ({Test, Line, Correct, Success}) => [ + + Test ('uses Z.equals for equality checks') + ([Line (1) ('> Absolute(-1)')]) + ([Line (2) ('Absolute(1)')]) + (Correct (Success (Absolute (-1)))), + ]; diff --git a/test/index.js b/test/index.js index c8c9005c..365156b7 100644 --- a/test/index.js +++ b/test/index.js @@ -6,6 +6,9 @@ import test from 'oletus'; import show from 'sanctuary-show'; import Z from 'sanctuary-type-classes'; +import {Incorrect, Correct} from '../lib/Comparison.js'; +import {Failure, Success} from '../lib/Effect.js'; +import {Line} from '../lib/Line.js'; import doctest from '../lib/doctest.js'; import resultsBin from './bin/results.js'; @@ -33,7 +36,29 @@ const eq = actual => expected => { strictEqual (Z.equals (actual, expected), true); }; -const testModule = (expecteds, path, options) => { +const Test = description => input => output => comparison => ({ + description, + expected: { + lines: { + input, + output, + }, + comparison, + }, +}); + +const dependencies = { + Test, + Line, + Incorrect, + Correct, + Failure, + Success, +}; + +const testModule = (module, path, options) => { + const expecteds = module (dependencies); + let promise = null; const run = () => (promise ?? (promise = doctest (options) (path))); @@ -48,7 +73,7 @@ const testModule = (expecteds, path, options) => { eq (actuals.length) (expecteds.length); }); - expecteds.forEach (([description, expected], idx) => { + expecteds.forEach (({description, expected}, idx) => { test (prefix + description, async () => { const actuals = await run (); eq (actuals[idx]) (expected); @@ -119,6 +144,7 @@ testModule (resultsStatements, 'test/statements/index.js', { }); testModule (resultsFantasyLand, 'test/fantasy-land/index.js', { + module: 'esm', silent: true, }); @@ -282,18 +308,25 @@ testCommand ('bin/doctest --print test/commonjs/exports/index.js', { status: 0, stdout: ` __doctest.enqueue({ - type: "input", - thunk: () => { - return exports.identity(42); + input: { + lines: [ + {number: 1, text: "> exports.identity(42)"}, + ], + thunk: () => { + return ( + exports.identity(42) + ); + }, }, -}); - -__doctest.enqueue({ - type: "output", - ":": 2, - "!": false, - thunk: () => { - return 42; + output: { + lines: [ + {number: 2, text: "42"}, + ], + thunk: () => { + return ( + 42 + ); + }, }, }); @@ -317,18 +350,25 @@ testCommand ('bin/doctest --print --module commonjs test/commonjs/exports/index. void (() => { __doctest.enqueue({ - type: "input", - thunk: () => { - return exports.identity(42); + input: { + lines: [ + {number: 1, text: "> exports.identity(42)"}, + ], + thunk: () => { + return ( + exports.identity(42) + ); + }, }, - }); - - __doctest.enqueue({ - type: "output", - ":": 2, - "!": false, - thunk: () => { - return 42; + output: { + lines: [ + {number: 2, text: "42"}, + ], + thunk: () => { + return ( + 42 + ); + }, }, }); diff --git a/test/line-endings/results.js b/test/line-endings/results.js index 0c3f1971..1c5a3dbb 100644 --- a/test/line-endings/results.js +++ b/test/line-endings/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'correct line number reported irrespective of line endings', - [ - true, - '42', - '42', - 2, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('correct line number reported irrespective of line endings') + ([Line (1) ('> 2 * 3 * 7')]) + ([Line (2) ('42')]) + (Correct (Success (42))), + ]; diff --git a/test/shared/results.coffee.js b/test/shared/results.coffee.js index e3ac1662..49e16361 100644 --- a/test/shared/results.coffee.js +++ b/test/shared/results.coffee.js @@ -1,236 +1,153 @@ -export default [ - [ - 'global variable accessible in outer scope', - [ - true, - '"global"', - '"global"', - 3, - ], - ], - [ - 'global variable accessible in inner scope', - [ - true, - '"global"', - '"global"', - 10, - ], - ], - [ - 'local variable referenced, not shadowed global', - [ - true, - '"shadowed"', - '"shadowed"', - 14, - ], - ], - [ - 'local variable accessible before declaration', - [ - true, - '2', - '2', - 20, - ], - ], - [ - 'assignment is an expression', - [ - true, - '3', - '3', - 25, - ], - ], - [ - 'variable declared in doctest remains accessible', - [ - true, - '[1, 2, 3]', - '[1, 2, 3]', - 28, - ], - ], - [ - 'arithmetic error reported', - [ - false, - '4', - '5', - 31, - ], - ], - [ - 'RangeError captured and reported', - [ - true, - '! RangeError', - '! RangeError', - 35, - ], - ], - [ - 'TypeError expected but not reported', - [ - false, - '0', - '! TypeError', - 38, - ], - ], - [ - 'function accessible before declaration', - [ - true, - '12', - '12', - 42, - ], - ], - [ - 'NaN can be used as expected result', - [ - true, - 'NaN', - 'NaN', - 45, - ], - ], - [ - 'function accessible after declaration', - [ - true, - '4', - '4', - 53, - ], - ], - [ - 'multiline input', - [ - true, - '[1, 2, 3, 4, 5, 6, 7, 8, 9]', - '[1, 2, 3, 4, 5, 6, 7, 8, 9]', - 65, - ], - ], - [ - 'multiline assignment', - [ - true, - '"input may span many lines"', - '"input may span many lines"', - 71, - ], - ], - [ - "spaces following '#' and '>' are optional", - [ - true, - '"no spaces"', - '"no spaces"', - 75, - ], - ], - [ - 'indented doctest', - [ - true, - '"Docco-compatible whitespace"', - '"Docco-compatible whitespace"', - 78, - ], - ], - [ - "'>' in doctest", - [ - true, - 'true', - 'true', - 81, - ], - ], - [ - 'comment on input line', - [ - true, - '"foobar"', - '"foobar"', - 85, - ], - ], - [ - 'comment on output line', - [ - true, - '25', - '25', - 88, - ], - ], - [ - 'variable in creation context is not accessible', - [ - true, - '"undefined"', - '"undefined"', - 92, - ], - ], - [ - "'.' should not follow leading '.' in multiline expressions", - [ - false, - '5', - '9.5', - 97, - ], - ], - [ - "wrapped lines may begin with more than one '.'", - [ - true, - '1234.5', - '1234.5', - 105, - ], - ], - [ - 'multiline comment', - [ - true, - '23', - '23', - 110, - ], - ], - [ - 'multiline comment with wrapped input', - [ - true, - '"FOO BAR"', - '"FOO BAR"', - 119, - ], - ], - [ - 'multiline output', - [ - true, - '["foo", "bar", "baz"]', - '["foo", "bar", "baz"]', - 140, - ], - ], - [ - 'multiline input with multiline output', - [ - true, - '["FOO", "BAR", "BAZ"]', - '["FOO", "BAR", "BAZ"]', - 149, - ], - ], +export default ({Test, Line, Incorrect, Correct, Failure, Success}) => [ + + Test ('global variable accessible in outer scope') + ([Line (2) ('> global')]) + ([Line (3) ('"global"')]) + (Correct (Success ('global'))), + + Test ('global variable accessible in inner scope') + ([Line (9) ('> global')]) + ([Line (10) ('"global"')]) + (Correct (Success ('global'))), + + Test ('local variable referenced, not shadowed global') + ([Line (13) ('> global')]) + ([Line (14) ('"shadowed"')]) + (Correct (Success ('shadowed'))), + + Test ('local variable accessible before declaration') + ([Line (19) ('> one * two')]) + ([Line (20) ('2')]) + (Correct (Success (2))), + + Test ('assignment is an expression') + ([Line (24) ('> @three = one + two')]) + ([Line (25) ('3')]) + (Correct (Success (3))), + + Test ('variable declared in doctest remains accessible') + ([Line (27) ('> [one, two, three]')]) + ([Line (28) ('[1, 2, 3]')]) + (Correct (Success ([1, 2, 3]))), + + Test ('arithmetic error reported') + ([Line (30) ('> two + two')]) + ([Line (31) ('5')]) + (Incorrect (Success (4)) + (Success (5))), + + Test ('RangeError captured and reported') + ([Line (34) ('> 0.toString 1')]) + ([Line (35) ('! RangeError')]) + (Correct (Failure (new RangeError ('toString() radix argument must be between 2 and 36')))), + + Test ('TypeError expected but not reported') + ([Line (37) ('> [].length')]) + ([Line (38) ('! TypeError')]) + (Incorrect (Success (0)) + (Failure (new TypeError ()))), + + Test ('function accessible before declaration') + ([Line (41) ('> double(6)')]) + ([Line (42) ('12')]) + (Correct (Success (12))), + + Test ('NaN can be used as expected result') + ([Line (44) ('> double()')]) + ([Line (45) ('NaN')]) + (Correct (Success (NaN))), + + Test ('function accessible after declaration') + ([Line (52) ('> double.call(null, 2)')]) + ([Line (53) ('4')]) + (Correct (Success (4))), + + Test ('multiline input') + ([Line (62) ('> [1,2,3,'), + Line (63) ('. 4,5,6,'), + Line (64) ('. 7,8,9]')]) + ([Line (65) ('[1,2,3,4,5,6,7,8,9]')]) + (Correct (Success ([1, 2, 3, 4, 5, 6, 7, 8, 9]))), + + Test ('multiline assignment') + ([Line (70) ('> string')]) + ([Line (71) ('"input may span many lines"')]) + (Correct (Success ('input may span many lines'))), + + Test ("spaces following '#' and '>' are optional") + ([Line (74) ('>"no spaces"')]) + ([Line (75) ('"no spaces"')]) + (Correct (Success ('no spaces'))), + + Test ('indented doctest') + ([Line (77) ('> "Docco-compatible whitespace"')]) + ([Line (78) ('"Docco-compatible whitespace"')]) + (Correct (Success ('Docco-compatible whitespace'))), + + Test ("'>' in doctest") + ([Line (80) ('> 2 > 1')]) + ([Line (81) ('true')]) + (Correct (Success (true))), + + Test ('comment on input line') + ([Line (84) ('> "foo" + "bar" # comment')]) + ([Line (85) ('"foobar"')]) + (Correct (Success ('foobar'))), + + Test ('comment on output line') + ([Line (87) ('> 5 * 5')]) + ([Line (88) ('25 # comment')]) + (Correct (Success (25))), + + Test ('variable in creation context is not accessible') + ([Line (91) ('> typeof text')]) + ([Line (92) ('"undefined"')]) + (Correct (Success ('undefined'))), + + Test ("'.' should not follow leading '.' in multiline expressions") + ([Line (95) ('>10 -'), + Line (96) ('..5')]) + ([Line (97) ('9.5')]) + (Incorrect (Success (5)) + (Success (9.5))), + + Test ("wrapped lines may begin with more than one '.'") + ([Line (100) ('> 1000 +'), + Line (101) ('.. 200 +'), + Line (102) ('... 30 +'), + Line (103) ('.... 4 +'), + Line (104) ('..... .5')]) + ([Line (105) ('1234.5')]) + (Correct (Success (1234.5))), + + Test ('multiline comment') + ([Line (109) ('> 3 ** 3 - 2 ** 2')]) + ([Line (110) ('23')]) + (Correct (Success (23))), + + Test ('multiline comment with wrapped input') + ([Line (115) ('> (["foo", "bar", "baz"]'), + Line (116) ('. .slice(0, -1)'), + Line (117) ('. .join(" ")'), + Line (118) ('. .toUpperCase())')]) + ([Line (119) ('"FOO BAR"')]) + (Correct (Success ('FOO BAR'))), + + Test ('multiline output') + ([Line (139) ('> ["foo", "bar", "baz"]')]) + ([Line (140) ('[ "foo"'), + Line (141) ('. "bar"'), + Line (142) ('. "baz" ]')]) + (Correct (Success (['foo', 'bar', 'baz']))), + + Test ('multiline input with multiline output') + ([Line (145) ('> ["foo", "bar", "baz"]'), + Line (146) ('. .join(",")'), + Line (147) ('. .toUpperCase()'), + Line (148) ('. .split(",")')]) + ([Line (149) ('[ "FOO"'), + Line (150) ('. "BAR"'), + Line (151) ('. "BAZ" ]')]) + (Correct (Success (['FOO', 'BAR', 'BAZ']))), + ]; diff --git a/test/shared/results.js b/test/shared/results.js index 9fd591ed..e1b4a2b6 100644 --- a/test/shared/results.js +++ b/test/shared/results.js @@ -1,272 +1,176 @@ -export default [ - [ - 'global variable accessible in outer scope', - [ - true, - '"global"', - '"global"', - 3, - ], - ], - [ - 'global variable accessible in inner scope', - [ - true, - '"global"', - '"global"', - 10, - ], - ], - [ - 'local variable referenced, not shadowed global', - [ - true, - '"shadowed"', - '"shadowed"', - 14, - ], - ], - [ - 'local variable accessible before declaration', - [ - true, - '2', - '2', - 20, - ], - ], - [ - 'assignment is an expression', - [ - true, - '3', - '3', - 25, - ], - ], - [ - 'variable declared in doctest remains accessible', - [ - true, - '[1, 2, 3]', - '[1, 2, 3]', - 28, - ], - ], - [ - 'arithmetic error reported', - [ - false, - '4', - '5', - 31, - ], - ], - [ - 'RangeError captured and reported', - [ - true, - '! RangeError', - '! RangeError', - 35, - ], - ], - [ - 'TypeError expected but not reported', - [ - false, - '0', - '! TypeError', - 38, - ], - ], - [ - 'function accessible before declaration', - [ - true, - '12', - '12', - 42, - ], - ], - [ - 'NaN can be used as expected result', - [ - true, - 'NaN', - 'NaN', - 45, - ], - ], - [ - 'function accessible after declaration', - [ - true, - '4', - '4', - 53, - ], - ], - [ - 'multiline input', - [ - true, - '[1, 2, 3, 4, 5, 6, 7, 8, 9]', - '[1, 2, 3, 4, 5, 6, 7, 8, 9]', - 65, - ], - ], - [ - 'multiline assignment', - [ - true, - '"input may span many lines"', - '"input may span many lines"', - 71, - ], - ], - [ - "spaces following '//' and '>' are optional", - [ - true, - '"no spaces"', - '"no spaces"', - 75, - ], - ], - [ - 'indented doctest', - [ - true, - '"Docco-compatible whitespace"', - '"Docco-compatible whitespace"', - 78, - ], - ], - [ - "'>' in doctest", - [ - true, - 'true', - 'true', - 81, - ], - ], - [ - 'comment on input line', - [ - true, - '"foobar"', - '"foobar"', - 85, - ], - ], - [ - 'comment on output line', - [ - true, - '25', - '25', - 88, - ], - ], - [ - 'variable in creation context is not accessible', - [ - true, - '"undefined"', - '"undefined"', - 92, - ], - ], - [ - "'.' should not follow leading '.' in multiline expressions", - [ - false, - '5', - '9.5', - 97, - ], - ], - [ - "wrapped lines may begin with more than one '.'", - [ - true, - '1234.5', - '1234.5', - 105, - ], - ], - [ - 'multiline comment', - [ - true, - '23', - '23', - 110, - ], - ], - [ - 'multiline comment with wrapped input', - [ - true, - '"FOO BAR"', - '"FOO BAR"', - 119, - ], - ], - [ - 'multiline comment with leading asterisks', - [ - true, - '25', - '25', - 125, - ], - ], - [ - 'multiline comment with leading asterisks', - [ - true, - '25', - '25', - 127, - ], - ], - [ - 'multiline comment with leading asterisks and wrapped input', - [ - true, - '55', - '55', - 135, - ], - ], - [ - 'multiline output', - [ - true, - '["foo", "bar", "baz"]', - '["foo", "bar", "baz"]', - 140, - ], - ], - [ - 'multiline input with multiline output', - [ - true, - '["FOO", "BAR", "BAZ"]', - '["FOO", "BAR", "BAZ"]', - 149, - ], - ], - [ - 'the rewriter should not rely on automatic semicolon insertion', - [ - false, - '"the rewriter should not rely"', - '"on automatic semicolon insertion"', - 155, - ], - ], +export default ({Test, Line, Incorrect, Correct, Failure, Success}) => [ + + Test ('global variable accessible in outer scope') + ([Line (2) ('> global')]) + ([Line (3) ('"global"')]) + (Correct (Success ('global'))), + + Test ('global variable accessible in inner scope') + ([Line (9) ('> global')]) + ([Line (10) ('"global"')]) + (Correct (Success ('global'))), + + Test ('local variable referenced, not shadowed global') + ([Line (13) ('> global')]) + ([Line (14) ('"shadowed"')]) + (Correct (Success ('shadowed'))), + + Test ('local variable accessible before declaration') + ([Line (19) ('> one * two')]) + ([Line (20) ('2')]) + (Correct (Success (2))), + + Test ('assignment is an expression') + ([Line (24) ('> three = one + two')]) + ([Line (25) ('3')]) + (Correct (Success (3))), + + Test ('variable declared in doctest remains accessible') + ([Line (27) ('> [one, two, three]')]) + ([Line (28) ('[1, 2, 3]')]) + (Correct (Success ([1, 2, 3]))), + + Test ('arithmetic error reported') + ([Line (30) ('> two + two')]) + ([Line (31) ('5')]) + (Incorrect (Success (4)) + (Success (5))), + + Test ('RangeError captured and reported') + ([Line (34) ('> 0..toString(1)')]) + ([Line (35) ('! RangeError')]) + (Correct (Failure (new RangeError ('toString() radix argument must be between 2 and 36')))), + + Test ('TypeError expected but not reported') + ([Line (37) ('> [].length')]) + ([Line (38) ('! TypeError')]) + (Incorrect (Success (0)) + (Failure (new TypeError ()))), + + Test ('function accessible before declaration') + ([Line (41) ('> double(6)')]) + ([Line (42) ('12')]) + (Correct (Success (12))), + + Test ('NaN can be used as expected result') + ([Line (44) ('> double()')]) + ([Line (45) ('NaN')]) + (Correct (Success (NaN))), + + Test ('function accessible after declaration') + ([Line (52) ('> double.call(null, 2)')]) + ([Line (53) ('4')]) + (Correct (Success (4))), + + Test ('multiline input') + ([Line (62) ('> [1,2,3,'), + Line (63) ('. 4,5,6,'), + Line (64) ('. 7,8,9]')]) + ([Line (65) ('[1,2,3,4,5,6,7,8,9]')]) + (Correct (Success ([1, 2, 3, 4, 5, 6, 7, 8, 9]))), + + Test ('multiline assignment') + ([Line (70) ('> string')]) + ([Line (71) ('"input may span many lines"')]) + (Correct (Success ('input may span many lines'))), + + Test ("spaces following '//' and '>' are optional") + ([Line (74) ('>"no spaces"')]) + ([Line (75) ('"no spaces"')]) + (Correct (Success ('no spaces'))), + + Test ('indented doctest') + ([Line (77) ('> "Docco-compatible whitespace"')]) + ([Line (78) ('"Docco-compatible whitespace"')]) + (Correct (Success ('Docco-compatible whitespace'))), + + Test ("'>' in doctest") + ([Line (80) ('> 2 > 1')]) + ([Line (81) ('true')]) + (Correct (Success (true))), + + Test ('comment on input line') + ([Line (84) ('> "foo" + "bar" // comment')]) + ([Line (85) ('"foobar"')]) + (Correct (Success ('foobar'))), + + Test ('comment on output line') + ([Line (87) ('> 5 * 5')]) + ([Line (88) ('25 // comment')]) + (Correct (Success (25))), + + Test ('variable in creation context is not accessible') + ([Line (91) ('> typeof text')]) + ([Line (92) ('"undefined"')]) + (Correct (Success ('undefined'))), + + Test ("'.' should not follow leading '.' in multiline expressions") + ([Line (95) ('>10 -'), + Line (96) ('..5')]) + ([Line (97) ('9.5')]) + (Incorrect (Success (5)) + (Success (9.5))), + + Test ("wrapped lines may begin with more than one '.'") + ([Line (100) ('> 1000 +'), + Line (101) ('.. 200 +'), + Line (102) ('... 30 +'), + Line (103) ('.... 4 +'), + Line (104) ('..... .5')]) + ([Line (105) ('1234.5')]) + (Correct (Success (1234.5))), + + Test ('multiline comment') + ([Line (109) ('> Math.pow(3, 3) - Math.pow(2, 2)')]) + ([Line (110) ('23')]) + (Correct (Success (23))), + + Test ('multiline comment with wrapped input') + ([Line (115) ('> ["foo", "bar", "baz"]'), + Line (116) ('. .slice(0, -1)'), + Line (117) ('. .join(" ")'), + Line (118) ('. .toUpperCase()')]) + ([Line (119) ('"FOO BAR"')]) + (Correct (Success ('FOO BAR'))), + + Test ('multiline comment with leading asterisks') + ([Line (124) ('> 1 + 2 * 3 * 4')]) + ([Line (125) ('25')]) + (Correct (Success (25))), + + Test ('multiline comment with leading asterisks') + ([Line (126) ('> 1 * 2 + 3 + 4 * 5')]) + ([Line (127) ('25')]) + (Correct (Success (25))), + + Test ('multiline comment with leading asterisks and wrapped input') + ([Line (132) ('> (function fib(n) {'), + Line (133) ('. return n == 0 || n == 1 ? n : fib(n - 2) + fib(n - 1);'), + Line (134) ('. })(10)')]) + ([Line (135) ('55')]) + (Correct (Success (55))), + + Test ('multiline output') + ([Line (139) ('> ["foo", "bar", "baz"]')]) + ([Line (140) ('[ "foo",'), + Line (141) ('. "bar",'), + Line (142) ('. "baz" ]')]) + (Correct (Success (['foo', 'bar', 'baz']))), + + Test ('multiline input with multiline output') + ([Line (145) ('> ["foo", "bar", "baz"]'), + Line (146) ('. .join(",")'), + Line (147) ('. .toUpperCase()'), + Line (148) ('. .split(",")')]) + ([Line (149) ('[ "FOO",'), + Line (150) ('. "BAR",'), + Line (151) ('. "BAZ" ]')]) + (Correct (Success (['FOO', 'BAR', 'BAZ']))), + + Test ('the rewriter should not rely on automatic semicolon insertion') + ([Line (154) ('> "the rewriter should not rely"')]) + ([Line (155) ('"on automatic semicolon insertion"')]) + (Incorrect (Success ('the rewriter should not rely')) + (Success ('on automatic semicolon insertion'))), + ]; diff --git a/test/statements/results.js b/test/statements/results.js index 0341ad25..63c55fd1 100644 --- a/test/statements/results.js +++ b/test/statements/results.js @@ -1,29 +1,18 @@ -export default [ - [ - 'var', - [ - true, - '8', - '8', - 4, - ], - ], - [ - 'let', - [ - true, - '1', - '1', - 9, - ], - ], - [ - 'function declaration', - [ - true, - '55', - '55', - 14, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('var') + ([Line (3) ('> Math.sqrt(x)')]) + ([Line (4) ('8')]) + (Correct (Success (8))), + + Test ('let') + ([Line (8) ('> Math.abs(y)')]) + ([Line (9) ('1')]) + (Correct (Success (1))), + + Test ('function declaration') + ([Line (13) ('> fib(10)')]) + ([Line (14) ('55')]) + (Correct (Success (55))), + ]; diff --git a/test/transcribe/results.js b/test/transcribe/results.js index dd57391d..71e9ac35 100644 --- a/test/transcribe/results.js +++ b/test/transcribe/results.js @@ -1,11 +1,8 @@ -export default [ - [ - 'accepts Transcribe-style prefix', - [ - true, - '[1, 2, 3]', - '[1, 2, 3]', - 12, - ], - ], +export default ({Test, Line, Correct, Success}) => [ + + Test ('accepts Transcribe-style prefix') + ([Line (11) ('> map(Math.sqrt)([1, 4, 9])')]) + ([Line (12) ('[1, 2, 3]')]) + (Correct (Success ([1, 2, 3]))), + ];