| 
 | 1 | +'use strict';  | 
 | 2 | + | 
 | 3 | +require('../common');  | 
 | 4 | +const ArrayStream = require('../common/arraystream');  | 
 | 5 | +const assert = require('assert');  | 
 | 6 | +const { describe, it } = require('node:test');  | 
 | 7 | + | 
 | 8 | +const repl = require('repl');  | 
 | 9 | + | 
 | 10 | +function getReplOutput(input, replOptions, run = true) {  | 
 | 11 | +  const inputStream = new ArrayStream();  | 
 | 12 | +  const outputStream = new ArrayStream();  | 
 | 13 | + | 
 | 14 | +  repl.start({  | 
 | 15 | +    input: inputStream,  | 
 | 16 | +    output: outputStream,  | 
 | 17 | +    ...replOptions,  | 
 | 18 | +  });  | 
 | 19 | + | 
 | 20 | +  let output = '';  | 
 | 21 | +  outputStream.write = (chunk) => (output += chunk);  | 
 | 22 | + | 
 | 23 | +  inputStream.emit('data', input);  | 
 | 24 | + | 
 | 25 | +  if (run) {  | 
 | 26 | +    inputStream.run(['']);  | 
 | 27 | +  }  | 
 | 28 | + | 
 | 29 | +  return output;  | 
 | 30 | +}  | 
 | 31 | + | 
 | 32 | +describe('repl with custom eval', { concurrency: true }, () => {  | 
 | 33 | +  it('uses the custom eval function as expected', () => {  | 
 | 34 | +    const output = getReplOutput('Convert this to upper case', {  | 
 | 35 | +      terminal: true,  | 
 | 36 | +      eval: (code, _ctx, _replRes, cb) => cb(null, code.toUpperCase()),  | 
 | 37 | +    });  | 
 | 38 | +    assert.match(  | 
 | 39 | +      output,  | 
 | 40 | +      /Convert this to upper case\r\n'CONVERT THIS TO UPPER CASE\\n'/  | 
 | 41 | +    );  | 
 | 42 | +  });  | 
 | 43 | + | 
 | 44 | +  it('surfaces errors as expected', () => {  | 
 | 45 | +    const output = getReplOutput('Convert this to upper case', {  | 
 | 46 | +      terminal: true,  | 
 | 47 | +      eval: (_code, _ctx, _replRes, cb) => cb(new Error('Testing Error')),  | 
 | 48 | +    });  | 
 | 49 | +    assert.match(output, /Uncaught Error: Testing Error\n/);  | 
 | 50 | +  });  | 
 | 51 | + | 
 | 52 | +  it('provides a repl context to the eval callback', async () => {  | 
 | 53 | +    const context = await new Promise((resolve) => {  | 
 | 54 | +      const r = repl.start({  | 
 | 55 | +        eval: (_cmd, context) => resolve(context),  | 
 | 56 | +      });  | 
 | 57 | +      r.context = { foo: 'bar' };  | 
 | 58 | +      r.write('\n.exit\n');  | 
 | 59 | +    });  | 
 | 60 | +    assert.strictEqual(context.foo, 'bar');  | 
 | 61 | +  });  | 
 | 62 | + | 
 | 63 | +  it('provides the global context to the eval callback', async () => {  | 
 | 64 | +    const context = await new Promise((resolve) => {  | 
 | 65 | +      const r = repl.start({  | 
 | 66 | +        useGlobal: true,  | 
 | 67 | +        eval: (_cmd, context) => resolve(context),  | 
 | 68 | +      });  | 
 | 69 | +      global.foo = 'global_foo';  | 
 | 70 | +      r.write('\n.exit\n');  | 
 | 71 | +    });  | 
 | 72 | + | 
 | 73 | +    assert.strictEqual(context.foo, 'global_foo');  | 
 | 74 | +    delete global.foo;  | 
 | 75 | +  });  | 
 | 76 | + | 
 | 77 | +  it('inherits variables from the global context but does not use it afterwords if `useGlobal` is false', async () => {  | 
 | 78 | +    global.bar = 'global_bar';  | 
 | 79 | +    const context = await new Promise((resolve) => {  | 
 | 80 | +      const r = repl.start({  | 
 | 81 | +        useGlobal: false,  | 
 | 82 | +        eval: (_cmd, context) => resolve(context),  | 
 | 83 | +      });  | 
 | 84 | +      global.baz = 'global_baz';  | 
 | 85 | +      r.write('\n.exit\n');  | 
 | 86 | +    });  | 
 | 87 | + | 
 | 88 | +    assert.strictEqual(context.bar, 'global_bar');  | 
 | 89 | +    assert.notStrictEqual(context.baz, 'global_baz');  | 
 | 90 | +    delete global.bar;  | 
 | 91 | +    delete global.baz;  | 
 | 92 | +  });  | 
 | 93 | + | 
 | 94 | +  /**  | 
 | 95 | +   * Default preprocessor transforms  | 
 | 96 | +   *  function f() {}  to  | 
 | 97 | +   *  var f = function f() {}  | 
 | 98 | +   * This test ensures that original input is preserved.  | 
 | 99 | +   * Reference: https://github.com/nodejs/node/issues/9743  | 
 | 100 | +   */  | 
 | 101 | +  it('preserves the original input', async () => {  | 
 | 102 | +    const cmd = await new Promise((resolve) => {  | 
 | 103 | +      const r = repl.start({  | 
 | 104 | +        eval: (cmd) => resolve(cmd),  | 
 | 105 | +      });  | 
 | 106 | +      r.write('function f() {}\n.exit\n');  | 
 | 107 | +    });  | 
 | 108 | +    assert.strictEqual(cmd, 'function f() {}\n');  | 
 | 109 | +  });  | 
 | 110 | + | 
 | 111 | +  it("doesn't show previews by default", () => {  | 
 | 112 | +    const input = "'Hello custom' + ' eval World!'";  | 
 | 113 | +    const output = getReplOutput(input, {  | 
 | 114 | +      terminal: true,  | 
 | 115 | +      eval: (code, _ctx, _replRes, cb) => cb(null, eval(code)),  | 
 | 116 | +    }, false);  | 
 | 117 | +    assert.strictEqual(output, input);  | 
 | 118 | +    assert.doesNotMatch(output, /Hello custom eval World!/);  | 
 | 119 | +  });  | 
 | 120 | + | 
 | 121 | +  it('does show previews if `preview` is set to `true`', () => {  | 
 | 122 | +    const input = "'Hello custom' + ' eval World!'";  | 
 | 123 | +    const output = getReplOutput(input, {  | 
 | 124 | +      terminal: true,  | 
 | 125 | +      eval: (code, _ctx, _replRes, cb) => cb(null, eval(code)),  | 
 | 126 | +      preview: true,  | 
 | 127 | +    }, false);  | 
 | 128 | + | 
 | 129 | +    const escapedInput = input.replace(/\+/g, '\\+'); // TODO: migrate to `RegExp.escape` when it's available.  | 
 | 130 | +    assert.match(  | 
 | 131 | +      output,  | 
 | 132 | +      new RegExp(`${escapedInput}\n// 'Hello custom eval World!'`)  | 
 | 133 | +    );  | 
 | 134 | +  });  | 
 | 135 | +});  | 
0 commit comments