From 3c91a7ae10f0ccabe4550c77189813f8d95785b0 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 6 Nov 2012 14:58:47 -0800 Subject: [PATCH] readline: use a "string_decoder" to parse "keypress" events While updating the readline test cases to test both "terimal: false" and "terminal: true" mode, it turned out that the test case testing utf8 chars being sent over multiple write() calls was failing. The solution is to use a string_decoder instance when parsing the "keypress" events. --- lib/readline.js | 8 +- test/simple/test-readline-interface.js | 192 +++++++++++++------------ 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index 2d62cb5c46..354b162843 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -801,12 +801,14 @@ exports.Interface = Interface; */ function emitKeypressEvents(stream) { - if (stream._emitKeypress) return; - stream._emitKeypress = true; + if (stream._keypressDecoder) return; + var StringDecoder = require('string_decoder').StringDecoder; // lazy load + stream._keypressDecoder = new StringDecoder('utf8'); function onData(b) { if (stream.listeners('keypress').length > 0) { - emitKey(stream, b); + var r = stream._keypressDecoder.write(b); + if (r) emitKey(stream, r); } else { // Nobody's watching anyway stream.removeListener('data', onData); diff --git a/test/simple/test-readline-interface.js b/test/simple/test-readline-interface.js index 33a5e7a16e..a3fde04481 100644 --- a/test/simple/test-readline-interface.js +++ b/test/simple/test-readline-interface.js @@ -32,100 +32,104 @@ function FakeInput() { inherits(FakeInput, EventEmitter); FakeInput.prototype.resume = function() {}; FakeInput.prototype.pause = function() {}; +FakeInput.prototype.write = function() {}; +FakeInput.prototype.end = function() {}; -var fi; -var rli; -var called; +[ true, false ].forEach(function(terminal) { + var fi; + var rli; + var called; -// sending a full line -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -called = false; -rli.on('line', function(line) { - called = true; - assert.equal(line, 'asdf'); + // sending a full line + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + called = false; + rli.on('line', function(line) { + called = true; + assert.equal(line, 'asdf'); + }); + fi.emit('data', 'asdf\n'); + assert.ok(called); + + // sending a blank line + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + called = false; + rli.on('line', function(line) { + called = true; + assert.equal(line, ''); + }); + fi.emit('data', '\n'); + assert.ok(called); + + // sending a single character with no newline + fi = new FakeInput(); + rli = new readline.Interface(fi, {}); + called = false; + rli.on('line', function(line) { + called = true; + }); + fi.emit('data', 'a'); + assert.ok(!called); + rli.close(); + + // sending a single character with no newline and then a newline + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + called = false; + rli.on('line', function(line) { + called = true; + assert.equal(line, 'a'); + }); + fi.emit('data', 'a'); + assert.ok(!called); + fi.emit('data', '\n'); + assert.ok(called); + rli.close(); + + // sending multiple newlines at once + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + var expectedLines = ['foo', 'bar', 'baz']; + var callCount = 0; + rli.on('line', function(line) { + assert.equal(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', expectedLines.join('\n') + '\n'); + assert.equal(callCount, expectedLines.length); + rli.close(); + + // sending multiple newlines at once that does not end with a new line + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + expectedLines = ['foo', 'bar', 'baz', 'bat']; + callCount = 0; + rli.on('line', function(line) { + assert.equal(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', expectedLines.join('\n')); + assert.equal(callCount, expectedLines.length - 1); + rli.close(); + + // sending a multi-byte utf8 char over multiple writes + var buf = Buffer('☮', 'utf8'); + fi = new FakeInput(); + rli = new readline.Interface({ input: fi, output: fi, terminal: terminal }); + callCount = 0; + rli.on('line', function(line) { + callCount++; + assert.equal(line, buf.toString('utf8')); + }); + [].forEach.call(buf, function(i) { + fi.emit('data', Buffer([i])); + }); + assert.equal(callCount, 0); + fi.emit('data', '\n'); + assert.equal(callCount, 1); + rli.close(); + + assert.deepEqual(fi.listeners('end'), []); + assert.deepEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); }); -fi.emit('data', 'asdf\n'); -assert.ok(called); - -// sending a blank line -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -called = false; -rli.on('line', function(line) { - called = true; - assert.equal(line, ''); -}); -fi.emit('data', '\n'); -assert.ok(called); - -// sending a single character with no newline -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -called = false; -rli.on('line', function(line) { - called = true; -}); -fi.emit('data', 'a'); -assert.ok(!called); -rli.close(); - -// sending a single character with no newline and then a newline -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -called = false; -rli.on('line', function(line) { - called = true; - assert.equal(line, 'a'); -}); -fi.emit('data', 'a'); -assert.ok(!called); -fi.emit('data', '\n'); -assert.ok(called); -rli.close(); - -// sending multiple newlines at once -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -var expectedLines = ['foo\n', 'bar\n', 'baz\n']; -var callCount = 0; -rli.on('line', function(line) { - assert.equal(line, expectedLines[callCount]); - callCount++; -}); -fi.emit('data', expectedLines.join('\n') + '\n'); -assert.equal(callCount, expectedLines.length); -rli.close(); - -// sending multiple newlines at once that does not end with a new line -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -var expectedLines = ['foo\n', 'bar\n', 'baz\n', 'bat']; -var callCount = 0; -rli.on('line', function(line) { - assert.equal(line, expectedLines[callCount]); - callCount++; -}); -fi.emit('data', expectedLines.join('\n')); -assert.equal(callCount, expectedLines.length - 1); -rli.close(); - -// sending a multi-byte utf8 char over multiple writes -var buf = Buffer('☮', 'utf8'); -fi = new FakeInput(); -rli = new readline.Interface(fi, {}); -callCount = 0; -rli.on('line', function(line) { - callCount++; - assert.equal(line, buf.toString('utf8')); -}); -[].forEach.call(buf, function(i) { - fi.emit('data', Buffer([i])); -}); -assert.equal(callCount, 0); -fi.emit('data', '\n'); -assert.equal(callCount, 1); -rli.close(); - -assert.deepEqual(fi.listeners('end'), []); -assert.deepEqual(fi.listeners('data'), []);