var assert = require('chai').assert; var expect = require('chai').expect; var Terminal = require('../build/xterm'); describe('xterm.js', function() { var xterm; beforeEach(function () { xterm = new Terminal(); xterm.refresh = function(){}; xterm.viewport = { syncScrollArea: function(){} }; }); describe('getOption', function() { it('should retrieve the option correctly', function() { // In the `options` namespace. xterm.options.cursorBlink = true; assert.equal(xterm.getOption('cursorBlink'), true); // On the Terminal instance delete xterm.options.cursorBlink; xterm.cursorBlink = false; assert.equal(xterm.getOption('cursorBlink'), false); }); it('should throw when retrieving a non-existant option', function() { assert.throws(xterm.getOption.bind(xterm, 'fake', true)); }); }); describe('setOption', function() { it('should set the option correctly', function() { xterm.setOption('cursorBlink', true); assert.equal(xterm.cursorBlink, true); assert.equal(xterm.options.cursorBlink, true); xterm.setOption('cursorBlink', false); assert.equal(xterm.cursorBlink, false); assert.equal(xterm.options.cursorBlink, false); }); it('should throw when setting a non-existant option', function() { assert.throws(xterm.setOption.bind(xterm, 'fake', true)); }); }); describe('clear', function() { it('should clear a buffer equal to rows', function() { var promptLine = xterm.lines[xterm.ybase + xterm.y]; xterm.clear(); assert.equal(xterm.y, 0); assert.equal(xterm.ybase, 0); assert.equal(xterm.ydisp, 0); assert.equal(xterm.lines.length, xterm.rows); assert.deepEqual(xterm.lines[0], promptLine); for (var i = 1; i < xterm.rows; i++) { assert.deepEqual(xterm.lines[0], xterm.blankLine()); } }); it('should clear a buffer larger than rows', function() { // Fill the buffer with dummy rows for (var i = 0; i < xterm.rows * 2; i++) { xterm.write('test\n'); } var promptLine = xterm.lines[xterm.ybase + xterm.y]; xterm.clear(); assert.equal(xterm.y, 0); assert.equal(xterm.ybase, 0); assert.equal(xterm.ydisp, 0); assert.equal(xterm.lines.length, xterm.rows); assert.deepEqual(xterm.lines[0], promptLine); for (var i = 1; i < xterm.rows; i++) { assert.deepEqual(xterm.lines[i], xterm.blankLine()); } }); it('should not break the prompt when cleared twice', function() { var promptLine = xterm.lines[xterm.ybase + xterm.y]; xterm.clear(); xterm.clear(); assert.equal(xterm.y, 0); assert.equal(xterm.ybase, 0); assert.equal(xterm.ydisp, 0); assert.equal(xterm.lines.length, xterm.rows); assert.deepEqual(xterm.lines[0], promptLine); for (var i = 1; i < xterm.rows; i++) { assert.deepEqual(xterm.lines[i], xterm.blankLine()); } }); }); describe('evaluateKeyEscapeSequence', function() { it('should return the correct escape sequence for unmodified keys', function() { // Backspace assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 8 }).key, '\x7f'); // ^? // Tab assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 9 }).key, '\t'); // Return/enter assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 13 }).key, '\r'); // CR // Escape assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 27 }).key, '\x1b'); // Page up, page down assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 33 }).key, '\x1b[5~'); // CSI 5 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 34 }).key, '\x1b[6~'); // CSI 6 ~ // End, Home assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 35 }).key, '\x1b[F'); // SS3 F assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 36 }).key, '\x1b[H'); // SS3 H // Left, up, right, down arrows assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 37 }).key, '\x1b[D'); // CSI D assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 38 }).key, '\x1b[A'); // CSI A assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 39 }).key, '\x1b[C'); // CSI C assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 40 }).key, '\x1b[B'); // CSI B // Insert assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 45 }).key, '\x1b[2~'); // CSI 2 ~ // Delete assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 46 }).key, '\x1b[3~'); // CSI 3 ~ // F1-F12 assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 112 }).key, '\x1bOP'); // SS3 P assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 113 }).key, '\x1bOQ'); // SS3 Q assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 114 }).key, '\x1bOR'); // SS3 R assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 115 }).key, '\x1bOS'); // SS3 S assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 116 }).key, '\x1b[15~'); // CSI 1 5 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 117 }).key, '\x1b[17~'); // CSI 1 7 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 118 }).key, '\x1b[18~'); // CSI 1 8 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 119 }).key, '\x1b[19~'); // CSI 1 9 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 120 }).key, '\x1b[20~'); // CSI 2 0 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 121 }).key, '\x1b[21~'); // CSI 2 1 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 122 }).key, '\x1b[23~'); // CSI 2 3 ~ assert.equal(xterm.evaluateKeyEscapeSequence({ keyCode: 123 }).key, '\x1b[24~'); // CSI 2 4 ~ }); it('should return \\x1b[3;5~ for ctrl+delete', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 46 }).key, '\x1b[3;5~'); }); it('should return \\x1b[3;2~ for shift+delete', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~'); }); it('should return \\x1b[3;3~ for alt+delete', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~'); }); it('should return \\x1b[5D for ctrl+left', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D }); it('should return \\x1b[5C for ctrl+right', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C }); it('should return \\x1b[5A for ctrl+up', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A }); it('should return \\x1b[5B for ctrl+down', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B }); // Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100 // http://unix.stackexchange.com/a/108106 it('should return \\x1b[5D for alt+left', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 37 }).key, '\x1b[1;5D'); // CSI 5 D }); it('should return \\x1b[5C for alt+right', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 39 }).key, '\x1b[1;5C'); // CSI 5 C }); it('should return \\x1b[5A for alt+up', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 38 }).key, '\x1b[1;5A'); // CSI 5 A }); it('should return \\x1b[5B for alt+down', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B }); it('should return the correct escape sequence for modified F1-F12 keys', function() { assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 112 }).key, '\x1b[1;2P'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 113 }).key, '\x1b[1;2Q'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 114 }).key, '\x1b[1;2R'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 115 }).key, '\x1b[1;2S'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 116 }).key, '\x1b[15;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 117 }).key, '\x1b[17;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 118 }).key, '\x1b[18;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 119 }).key, '\x1b[19;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 120 }).key, '\x1b[20;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 121 }).key, '\x1b[21;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 122 }).key, '\x1b[23;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ shiftKey: true, keyCode: 123 }).key, '\x1b[24;2~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 112 }).key, '\x1b[1;3P'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 113 }).key, '\x1b[1;3Q'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 114 }).key, '\x1b[1;3R'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 115 }).key, '\x1b[1;3S'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 116 }).key, '\x1b[15;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 117 }).key, '\x1b[17;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 118 }).key, '\x1b[18;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 119 }).key, '\x1b[19;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 120 }).key, '\x1b[20;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 121 }).key, '\x1b[21;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 122 }).key, '\x1b[23;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ altKey: true, keyCode: 123 }).key, '\x1b[24;3~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 112 }).key, '\x1b[1;5P'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 113 }).key, '\x1b[1;5Q'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 114 }).key, '\x1b[1;5R'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 115 }).key, '\x1b[1;5S'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 116 }).key, '\x1b[15;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 117 }).key, '\x1b[17;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 118 }).key, '\x1b[18;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 119 }).key, '\x1b[19;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 120 }).key, '\x1b[20;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 121 }).key, '\x1b[21;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~'); assert.equal(xterm.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~'); }); }); describe('attachCustomEventHandler', function () { var evKeyDown = { preventDefault: function() {}, stopPropagation: function() {}, type: 'keydown' } beforeEach(function() { xterm.handler = function() {}; xterm.showCursor = function() {}; xterm.clearSelection = function() {}; xterm.compositionHelper = { keydown: { bind: function() { return function () { return true; } } } } }); it('should process the keydown event based on what the handler returns', function () { assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); xterm.attachCustomKeydownHandler(function (ev) { return ev.keyCode === 77; }); assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), true); xterm.attachCustomKeydownHandler(function (ev) { return ev.keyCode !== 77; }); assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); }); it('should alive after reset(ESC c Full Reset (RIS))', function () { xterm.attachCustomKeydownHandler(function (ev) { return ev.keyCode !== 77; }); assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); xterm.reset(); assert.equal(xterm.keyDown(Object.assign({}, evKeyDown, { keyCode: 77 })), false); }); }); describe('evaluateCopiedTextProcessing', function () { it('should strip trailing whitespaces and replace nbsps with spaces', function () { var nonBreakingSpace = String.fromCharCode(160), copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace, processedText = Terminal.prepareCopiedTextForClipboard(copiedText); // No trailing spaces assert.equal(processedText.match(/\s+$/), null); // No non-breaking space assert.equal(processedText.indexOf(nonBreakingSpace), -1); }); }); describe('Third level shift', function() { var evKeyDown = { preventDefault: function() {}, stopPropagation: function() {}, type: 'keydown' }, evKeyPress = { preventDefault: function() {}, stopPropagation: function() {}, type: 'keypress' }; beforeEach(function() { xterm.handler = function() {}; xterm.showCursor = function() {}; xterm.clearSelection = function() {}; xterm.compositionHelper = { isComposing: false, keydown: { bind: function() { return function() { return true; }; } } }; }); describe('On Mac OS', function() { beforeEach(function() { xterm.isMac = true; }); it('should not interfere with the alt key on keyDown', function() { assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 81 })), true ); assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 192 })), true ); }); it('should interefere with the alt + arrow keys', function() { assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 37 })), false ); assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, keyCode: 39 })), false ); }); it('should emit key with alt + key on keyPress', function(done) { var keys = ['@', '@', '\\', '\\', '|', '|']; xterm.on('keypress', function(key) { if (key) { var index = keys.indexOf(key); assert(index !== -1, "Emitted wrong key: " + key); keys.splice(index, 1); } if (keys.length === 0) done(); }); xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 64 })); // @ // Firefox xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 64, keyCode: 0 })); xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 92 })); // \ xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 92, keyCode: 0 })); xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, keyCode: 124 })); // | xterm.keyPress(Object.assign({}, evKeyPress, { altKey: true, charCode: 124, keyCode: 0 })); }); }); describe('On MS Windows', function() { beforeEach(function() { xterm.isMSWindows = true; }); it('should not interfere with the alt + ctrl key on keyDown', function() { assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 81 })), true ); assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 192 })), true ); }); it('should interefere with the alt + ctrl + arrow keys', function() { assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 37 })), false ); assert.equal( xterm.keyDown(Object.assign({}, evKeyDown, { altKey: true, ctrlKey: true, keyCode: 39 })), false ); }); it('should emit key with alt + ctrl + key on keyPress', function(done) { var keys = ['@', '@', '\\', '\\', '|', '|']; xterm.on('keypress', function(key) { if (key) { var index = keys.indexOf(key); assert(index !== -1, "Emitted wrong key: " + key); keys.splice(index, 1); } if (keys.length === 0) done(); }); xterm.keyPress( Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 64 }) ); // @ xterm.keyPress( Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 64, keyCode: 0 }) ); xterm.keyPress( Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 92 }) ); // \ xterm.keyPress( Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 92, keyCode: 0 }) ); xterm.keyPress( Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, keyCode: 124 }) ); // | xterm.keyPress( Object.assign({}, evKeyPress, { altKey: true, ctrlKey: true, charCode: 124, keyCode: 0 }) ); }); }); }); describe('unicode - surrogates', function() { it('2 characters per cell', function () { var high = String.fromCharCode(0xD800); for (var i=0xDC00; i<=0xDCFF; ++i) { xterm.write(high + String.fromCharCode(i)); var tchar = xterm.lines[0][0]; expect(tchar[1]).eql(high + String.fromCharCode(i)); expect(tchar[1].length).eql(2); expect(tchar[2]).eql(1); expect(xterm.lines[0][1][1]).eql(' '); xterm.reset(); } }); it('2 characters at last cell', function() { var high = String.fromCharCode(0xD800); for (var i=0xDC00; i<=0xDCFF; ++i) { xterm.x = xterm.cols - 1; xterm.write(high + String.fromCharCode(i)); expect(xterm.lines[0][xterm.x-1][1]).eql(high + String.fromCharCode(i)); expect(xterm.lines[0][xterm.x-1][1].length).eql(2); expect(xterm.lines[1][0][1]).eql(' '); xterm.reset(); } }); it('2 characters per cell over line end with autowrap', function() { var high = String.fromCharCode(0xD800); for (var i=0xDC00; i<=0xDCFF; ++i) { xterm.x = xterm.cols - 1; xterm.wraparoundMode = true; xterm.write('a' + high + String.fromCharCode(i)); expect(xterm.lines[0][xterm.cols-1][1]).eql('a'); expect(xterm.lines[1][0][1]).eql(high + String.fromCharCode(i)); expect(xterm.lines[1][0][1].length).eql(2); expect(xterm.lines[1][1][1]).eql(' '); xterm.reset(); } }); it('2 characters per cell over line end without autowrap', function() { var high = String.fromCharCode(0xD800); for (var i=0xDC00; i<=0xDCFF; ++i) { xterm.x = xterm.cols - 1; xterm.wraparoundMode = false; xterm.write('a' + high + String.fromCharCode(i)); expect(xterm.lines[0][xterm.cols-1][1]).eql(high + String.fromCharCode(i)); expect(xterm.lines[0][xterm.cols-1][1].length).eql(2); expect(xterm.lines[1][1][1]).eql(' '); xterm.reset(); } }); it('splitted surrogates', function() { var high = String.fromCharCode(0xD800); for (var i=0xDC00; i<=0xDCFF; ++i) { xterm.write(high); xterm.write(String.fromCharCode(i)); var tchar = xterm.lines[0][0]; expect(tchar[1]).eql(high + String.fromCharCode(i)); expect(tchar[1].length).eql(2); expect(tchar[2]).eql(1); expect(xterm.lines[0][1][1]).eql(' '); xterm.reset(); } }); }); describe('unicode - combining characters', function() { it('café', function () { xterm.write('cafe\u0301'); expect(xterm.lines[0][3][1]).eql('e\u0301'); expect(xterm.lines[0][3][1].length).eql(2); expect(xterm.lines[0][3][2]).eql(1); }); it('café - end of line', function() { xterm.x = xterm.cols - 1 - 3; xterm.write('cafe\u0301'); expect(xterm.lines[0][xterm.cols-1][1]).eql('e\u0301'); expect(xterm.lines[0][xterm.cols-1][1].length).eql(2); expect(xterm.lines[0][xterm.cols-1][2]).eql(1); expect(xterm.lines[0][1][1]).eql(' '); expect(xterm.lines[0][1][1].length).eql(1); expect(xterm.lines[0][1][2]).eql(1); }); it('multiple combined é', function() { xterm.wraparoundMode = true; xterm.write(Array(100).join('e\u0301')); for (var i=0; i