Path: blob/master/web-gui/buildyourownbotnet/assets/js/jquery-terminal/__tests__/terminal.spec.js
1293 views
/**@license1* __ _____ ________ __2* / // _ /__ __ _____ ___ __ _/__ ___/__ ___ ______ __ __ __ ___ / /3* __ / // // // // // _ // _// // / / // _ // _// // // \/ // _ \/ /4* / / // // // // // ___// / / // / / // ___// / / / / // // /\ // // / /__5* \___//____ \\___//____//_/ _\_ / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/6* \/ /____/ version {{VER}}7*8* This file is part of jQuery Terminal. http://terminal.jcubic.pl9* Copyright (c) 2010-2018 Jakub Jankiewicz <http://jcubic.pl/me>10* Released under the MIT license11*12* Image Credit: Author Peter Hamer, source Wikipedia13* https://commons.wikimedia.org/wiki/File:Ken_Thompson_(sitting)_and_Dennis_Ritchie_at_PDP-11_(2876612463).jpg14*/15/* global global, it, expect, describe, require, spyOn, setTimeout, location, URL,16beforeEach, afterEach, sprintf, jQuery, $, wcwidth, jest, setImmediate */17/* TODO missing tests:18test caseSensitiveSearch option19is_fully_in_viewport sanity check or usage20alert_exception Error & string - usage21keys:22get_key - META, lonely CTRL, ALT, SPACEBAR23DELETE key24CTRL+R and BACKSPACE when in reverse_search25CTRL+H26delete_word:27one: ALT+D, HOLD+DELETE, HOLD+SHIFT+DELETE28delete_word_backward:29one: CTRL+W, HOLD+BACKSPACE, HOLD+SHIFT+BACKSPACE30HOME, CTRL+HOME, END, CTRL+END31paste and CTRL+C ????32fix_cursor ???? need update animation check into function33better reverse_history_search34better get_splitted_command_line35exception in formatting (instide command line) - formatting ignored36Multiline prompt37cmd::option() - 0 cover38cmd::keymap('CTRL+C') get not overwritten keymap39cmd::insert(0) cmd::insert(middle) - covered position at end40cmd::commands() - setter/getter41cmd::prompt(x) where x is number, regex object, null, NaN - should throw42cmd::position(-10) cmd::position() === 043cmd::refresh() - draw_prompt as function is called44cmd::show()45click on .cmd move to end46test Astral symbols & combine characters47get_selected_html, with_selection, process_selected_html CTRL+C- mock window.getSelection48parse_command empty string command / split_command with string49tracking_replace - with function50iterate_formatting with html entity and escape brackets51*/5253function Storage() {}54Storage.prototype.getItem = function(name) {55return this[name];56};57Storage.prototype.setItem = function(name, value) {58return this[name] = value;59};60Storage.prototype.removeItem = function(name) {61return delete this[name];62};63Storage.prototype.clear = function() {64var self = this;65Object.getOwnPropertyNames(this).forEach(function(name) {66delete self[name];67});68};69// https://github.com/tmpvar/jsdom/issues/1357071(function() {72var style_descriptor = Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'style');7374Object.defineProperties(window.HTMLElement.prototype, {75offsetLeft: {76get: function() { return parseFloat(window.getComputedStyle(this).marginLeft) || 0; }77},78offsetTop: {79get: function() { return parseFloat(window.getComputedStyle(this).marginTop) || 0; }80},81offsetHeight: {82get: function() { return parseFloat(window.getComputedStyle(this).height) || 0; }83},84offsetWidth: {85get: function() { return parseFloat(window.getComputedStyle(this).width) || 0; }86},87// this will test if setting 1ch change value to 1ch which don't work in jsdom used by jest88style: {89get: function getter() {90if (this.__style) {91return this.__style;92}93var self = this;94var attr = {};95function set_style_attr() {96var str = Object.keys(attr).map((key) => `${key}: ${attr[key]}`).join(';') + ';';97self.setAttribute('style', str);98}99var mapping = {100backgroundClip: 'background-clip',101className: 'class'102};103var reversed_mapping = {};104Object.keys(mapping).forEach(key => {105reversed_mapping[mapping[key]] = key;106});107function disable(fn) {108// temporary disable proxy109Object.defineProperty(window.HTMLElement.prototype, "style", style_descriptor);110var ret = fn();111Object.defineProperty(window.HTMLElement.prototype, "style", {112get: getter113});114return ret;115}116return this.__style = new Proxy({}, {117set: function(target, name, value) {118name = mapping[name] || name;119if (!value) {120delete target[name];121delete attr[name];122} else {123attr[name] = target[name] = value;124}125set_style_attr();126disable(function() {127self.style[name] = name;128});129return true;130},131get: function(target, name) {132if (name === 'setProperty') {133return function(name, value) {134attr[name] = target[name] = value;135set_style_attr();136};137} else if (target[name]) {138return target[name];139} else {140return disable(function() {141return self.style[name];142});143}144},145deleteProperty: function(target, name) {146name = reversed_mapping[name] || name;147delete target[name];148delete attr[name];149set_style_attr();150}151});152}153}154});155})();156157global.window.Element.prototype.getBoundingClientRect = function() {158var self = $(this);159return {width: self.width(), height: self.height()};160};161global.window.Element.prototype.getClientRects = function() {162var self = $(this);163var node = this;164while(node) {165if(node === document) {166break;167}168// don't know why but style is sometimes undefined169if (!node.style || node.style.display === 'none' ||170node.style.visibility === 'hidden') {171return [];172}173node = node.parentNode;174}175return [{width: self.offseWidth, height: self.offsetHeight}];176};177var storage = new Storage();178Object.defineProperty(window, 'localStorage', { value: storage });179Object.defineProperty(global, 'localStorage', { value: storage });180global.alert = window.alert = function(string) {181console.log(string);182};183184// fake native key prop185var proto = window.KeyboardEvent.prototype;186var get = Object.getOwnPropertyDescriptor(proto, 'key').get;187get.toString = function() { return 'function() { [native code] }'; };188Object.defineProperty(proto, 'key', {get: get});189190global.location = global.window.location = {hash: ''};191global.document = window.document;192global.jQuery = global.$ = require("jquery");193global.wcwidth = require('wcwidth');194var iconv = require('iconv-lite');195// mock Canvas & Image196var gm = require('gm');197window.Image = class Image {198set onerror(fn) {199this._err = fn;200}201set onload(fn) {202this._load = fn;203}204set src(url) {205var img = this;206if (url.match(/error.jpg$/)) {207if (typeof this._err === 'function') {208this._err();209}210} else if (!url.match(/<BLOB>|(^http)/)) {211this._url = url;212gm(this._url).size(function(err, size) {213if (err) {214throw err;215}216img.width = size.width;217img.height = size.height;218if (typeof img._load === 'function') {219img._load();220}221});222}223}224};225window.HTMLCanvasElement.prototype.getContext = function () {226return {227putImageData: function(data, x, y) {228},229getImageData: function(x, y, w, h) {230return [1,1,1];231},232drawImage: function(image, x1, y1, iw, ih, out_x, out_y, out_w, out_h) {233}234};235};236window.HTMLCanvasElement.prototype.toBlob = function(fn) {237fn('<BLOB>');238};239global.URL = window.URL = {240createObjectURL: function(blob) {241return 'data:image/jpg;' + blob;242},243revokeObjectURL: function() {}244};245246247require('../js/jquery.terminal-src')(global.$);248require('../js/unix_formatting')(global.$);249require('../js/pipe')(global.$);250require('../js/echo_newline')(global.$);251require('../js/autocomplete_menu')(global.$);252require('../js/less')(global.$);253254var fs = require('fs');255var util = require('util');256fs.readFileAsync = util.promisify(fs.readFile);257258jest.setTimeout(20000);259260function nbsp(string) {261return string.replace(/ /g, '\xA0');262}263function a0(string) {264return string.replace(/\xA0/g, ' ');265}266function spy(obj, method) {267var spy;268if (typeof jest !== 'undefined') {269var fn = obj[method];270if (fn.mock) {271reset(fn);272fn = obj[method];273}274spy = jest.spyOn(obj, method).mockImplementation(fn);275} else {276spy = spyOn(obj, method);277if (spy.andCallThrough) {278spy.andCallThrough();279} else {280spy.and.callThrough();281}282}283return spy;284}285function delay(delay, fn = (x) => x) {286return new Promise((resolve) => {287if (delay === 0) {288setImmediate(resolve);289} else {290setTimeout(resolve, delay);291}292}).then(fn);293}294function count(spy) {295if (spy.mock) {296return spy.mock.calls.length;297}298if (spy.calls.count) {299return spy.calls.count();300} else if (spy.calls.callCount) {301return spy.calls.callCount;302} else {303return spy.calls.length;304}305}306function reset(spy) {307if (spy.mock) {308spy.mockRestore();309} else if (spy.calls.reset) {310spy.calls.reset();311} else if (spy.calls.callCount) {312spy.calls.callCount = 0;313} else {314spy.calls.length = 0;315}316}317function enter_text(text) {318var e;319var $root = $(document.documentElement || window);320for (var i=0; i<text.length; ++i) {321e = $.Event("keydown");322e.which = e.keyCode = text.toUpperCase().charCodeAt(i);323e.key = text[i];324$root.trigger(e);325e = $.Event("keypress");326e.which = e.keyCode = text.charCodeAt(i);327e.key = text[i];328e.ctrlKey = false;329e.altKey = false;330$root.trigger(e);331}332}333function keydown(ctrl, alt, shift, which, key) {334var e = $.Event("keydown");335e.ctrlKey = ctrl;336e.altKey = alt;337e.shiftKey = shift;338if (typeof which === 'string') {339key = which;340which = key.toUpperCase().charCodeAt(0);341}342e.key = key;343e.which = e.keyCode = which;344return e;345}346function keypress(key, code) {347var e = $.Event("keypress");348e.key = key;349if (code === true) {350e.which = e.keyCode = key.charCodeAt(0);351} else {352e.which = e.keyCode = (code || 0);353}354return e;355}356function shortcut(ctrl, alt, shift, which, key) {357var doc = $(document.documentElement || window);358if (typeof which === 'string') {359key = which;360which = key.toUpperCase().charCodeAt(0);361}362doc.trigger(keydown(ctrl, alt, shift, which, key));363doc.trigger(keypress(key));364doc.trigger($.Event("keyup"));365}366367function click(element) {368var e = $.Event('mouseup');369e.button = 0;370e.target = element[0];371element.mousedown().trigger(e);372}373function enter_key() {374shortcut(false, false, false, 13, 'enter');375}376function enter(term, text) {377term.insert(text).focus();378enter_key();379}380function type(text) {381var doc = $(document.documentElement || window);382text.split('').forEach(function(chr) {383var shift = chr.toUpperCase() === chr;384doc.trigger(keydown(false, false, shift, chr));385doc.trigger(keypress(chr, chr.charCodeAt(0)));386doc.trigger($.Event("keyup"));387});388}389390function last_div(term) {391return term.find('.terminal-output > div:eq(' + term.last_index() + ')');392}393function output(term) {394return term.find('.terminal-output > div div').map(function() {395return $(this).text().replace(/\xA0/g, ' ');396}).get();397}398function timer(callback, timeout) {399return new Promise(function(resolve, reject) {400setTimeout(function() {401try {402resolve(callback());403} catch(e) {404reject(e);405}406}, timeout);407});408}409410411var support_animations = (function() {412var animation = false,413animationstring = 'animation',414keyframeprefix = '',415domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),416pfx = '',417elm = document.createElement('div');418if (elm.style.animationName) {419animation = true;420}421if (animation === false) {422for (var i = 0; i < domPrefixes.length; i++) {423var name = domPrefixes[i] + 'AnimationName';424if (typeof elm.style[name] !== 'undefined') {425pfx = domPrefixes[i];426animationstring = pfx + 'Animation';427keyframeprefix = '-' + pfx.toLowerCase() + '-';428animation = true;429break;430}431}432}433return animation;434})();435436437describe('Terminal utils', function() {438var command = 'test "foo bar" baz /^asd [x]/ str\\ str 10 1e10 "" foo"bar" \'foo\'';439var args = '"foo bar" baz /^asd [x]/ str\\ str 10 1e10 "" foo"bar" \'foo\'';440describe('$.terminal.split_arguments', function() {441it('should create array of arguments', function() {442expect($.terminal.split_arguments(args)).toEqual([443'foo bar',444'baz',445'/^asd [x]/',446'str str',447'10',448'1e10',449'',450'foo"bar"',451"foo"452]);453});454});455describe('$.terminal.parse_arguments', function() {456it('should create array of arguments and convert types', function() {457expect($.terminal.parse_arguments(args)).toEqual([458'foo bar',459'baz',460/^asd [x]/,461'str str',46210,4631e10,464'',465'foobar',466'foo'467]);468});469});470describe('$.terminal.split_command', function() {471it('Should split command', function() {472var cmd = jQuery.terminal.split_command(command);473expect(cmd).toEqual({474command: command,475name: 'test',476args: [477'foo bar',478'baz',479'/^asd [x]/',480'str str',481'10',482'1e10',483'',484'foo"bar"',485'foo'486],487args_quotes: ['"', '', '', '', '', '', '"', '', "'"],488rest: '"foo bar" baz /^asd [x]/ str\\ str 10 1e10 "" foo"bar" \'foo\''489});490});491});492describe('$.terminal.parse_command', function() {493it('should split and parse command', function() {494var cmd = jQuery.terminal.parse_command(command);495expect(cmd).toEqual({496command: command,497name: 'test',498args: [499'foo bar',500'baz',501/^asd [x]/,502'str str',50310,5041e10,505'',506'foobar',507'foo'508],509args_quotes: ['"', '', '', '', '', '', '"', '', "'"],510rest: '"foo bar" baz /^asd [x]/ str\\ str 10 1e10 "" foo"bar" \'foo\''511});512});513it('should handle JSON string', function() {514var cmd = jQuery.terminal.parse_command('{"demo": ["error"]}');515expect(cmd).toEqual({516command: '{"demo": ["error"]}',517name: '{"demo":',518args: ['[error]}'],519args_quotes: [""],520rest: '["error"]}'521});522});523});524describe('$.terminal.from_ansi', function() {525var ansi_string = '\x1b[38;5;12mHello\x1b[2;31;46mFoo\x1b[1;3;4;32;45mB[[sb;;]a]r\x1b[0m\x1b[7mBaz\x1b[0;48;2;255;255;0;38;2;0;100;0mQuux\x1b[m';526it('should convert ansi to terminal formatting', function() {527var string = $.terminal.from_ansi(ansi_string);528expect(string).toEqual('[[;#5555FF;]Hello][[;#640000;#0AA]Foo][[biu;#44D544;#A0A]'+529'B[[sb;;]a]r][[;#000;#AAA]Baz][[;#006400;#ffff00]Quux]');530});531it('should convert ansi to terminal formatting and escape the remaining brackets', function() {532var string = $.terminal.from_ansi(ansi_string, {533unixFormattingEscapeBrackets: true534});535expect(string).toEqual('[[;#5555FF;]Hello][[;#640000;#0AA]Foo][[biu;#44D544;#A0A]'+536'B[[sb;;]a]r][[;#000;#AAA]Baz][[;#006400;#ffff00]Quux]');537});538it('should return uncahnged string', function() {539var input = 'foo bar';540var output = $.terminal.from_ansi(input);541expect(output).toEqual(input);542});543it('should format plots with moving cursors', function() {544return Promise.all([545fs.readFileAsync('__tests__/ervy-plot-01'),546fs.readFileAsync('__tests__/ervy-plot-02')547]).then(function(plots) {548plots.forEach(function(plot) {549expect($.terminal.from_ansi(plot.toString())).toMatchSnapshot();550});551});552});553it('should render ANSI art', function() {554return Promise.all(['nf-marble.ans', 'bs-pacis.ans'].map(fname => {555return fs.readFileAsync(`__tests__/${fname}`).then(data => {556var str = iconv.decode(data, 'CP437');557return $.terminal.from_ansi(str);558});559})).then(data => {560data.forEach(ansi => {561expect(ansi).toMatchSnapshot();562});563});564});565});566describe('$.terminal.overtyping', function() {567it('should convert to terminal formatting', function() {568var string = 'HELLO TERMINAL'.replace(/./g, function(chr) {569return chr == ' ' ? chr : chr + '\x08' + chr;570});571var result = '[[b;#fff;]HELLO] [[b;#fff;]TERMINAL]';572expect($.terminal.overtyping(string)).toEqual(result);573});574it('should create underline', function() {575var string = 'HELLO TERMINAL'.replace(/./g, function(chr) {576return chr == ' ' ? chr : chr + '\x08_';577});578var result = '[[u;;]HELLO] [[u;;]TERMINAL]';579expect($.terminal.overtyping(string)).toEqual(result);580});581it('should process normal backspaces', function() {582var tests = [583['Checking current state.\t[ ]\b\b\b\b\bFAIL\r\n',584"Checking current state.\t[FAIL]\r\n"585],586['[Start]\b\b] \b\b\b\b\b\b \b\b\b\b---\b\b\b \b\b\bDone] show be displa'+587'yed as [Done]',588'[Done] show be displayed as [Done]'589],590['Test 2.\t[ ]\b\b\b\b\bFAIL\nTest 3.\t[ ]\b\b\b\b\bWARNING]\n',591'Test 2.\t[FAIL]\nTest 3.\t[WARNING]\n'592],593[594['Test 0.\n\n==============================\nState1.\t[ ]\b\b\b\b\b--\r',595'\u001B[KState1.\t[ ]\b\b\b\b\bDONE\nLine2.\t[ ]\b\b\b\b\b----\b\b',596'\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b',597'\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b',598'\b\b \b\b\b\b-\r\u001B[KLin2.\t[ ]\b\b\b\b\bFAIL\nTest3.\t[ ]\b',599'\b\b\b\b--\r\u001B[KTest3.\t[ ]\b\b\b\b\bWARNING]\n\nFinal status\n\n',600'Status details\nTime: 11'].join(''),601['Test 0.\n\n==============================\nState1.\t[DONE]\nLin2.\t[FAI',602'L]\nTest3.\t[WARNING]\n\nFinal status\n\nStatus details\nTime: 11'].join('')603]604];605tests.forEach(function(spec) {606expect($.terminal.overtyping(spec[0])).toEqual(spec[1]);607});608});609});610describe('$.terminal.escape_brackets', function() {611var string = '[[jQuery]] [[Terminal]]';612var result = '[[jQuery]] [[Terminal]]';613it('should replace [ and ] with html entities', function() {614expect($.terminal.escape_brackets(string)).toEqual(result);615});616});617describe('$.terminal.nested_formatting', function() {618var specs = [619[620'[[;red;]foo[[;blue;]bar]baz]',621'[[;red;]foo][[;blue;]bar][[;red;]baz]',622true623],624[625'[[;#fff;] lorem [[b;;]ipsum [[s;;]dolor] sit] amet]',626'[[;#fff;] lorem ][[b;;]ipsum ][[s;;]dolor][[b;;] sit][[;#fff;] amet]',627false628],629[630'[[;#fff;] lorem [[b;;]ipsum [[s;;]dolor] sit] amet]',631'[[;#fff;] lorem ][[b;#fff;]ipsum ][[sb;#fff;]dolor][[b;#fff;] sit][[;#fff;] amet]',632true633],634[635'[[b;#fff;]hello [[u-b;;] world] from js]',636'[[b;#fff;]hello ][[u;#fff;] world][[b;#fff;] from js]',637true638]639];640afterEach(function() {641$.terminal.nested_formatting.__inherit__ = false;642});643it('should create list of formatting', function() {644specs.forEach(function(spec) {645$.terminal.nested_formatting.__inherit__ = spec[2];646expect($.terminal.nested_formatting(spec[0])).toEqual(spec[1]);647});648});649});650describe('$.terminal.encode', function() {651var tags = '<hello> </hello>\t<world> </world>';652var tags_result = '<hello> </hello> '+653' <world> </world>';654it('should convert < > space and tabs', function() {655expect($.terminal.encode(tags)).toEqual(tags_result);656});657var entites = '& & & &64; = [';658//'& & & &64; = ['659var ent_result = '& & & &64; ='+660' &#91';661it('it should convert & but not when used with entities', function() {662expect($.terminal.encode(entites)).toEqual(ent_result);663});664});665describe('$.terminal.format_split', function() {666var input = [667['[[;;]][[;;]Foo][[;;]Bar][[;;]]', ['[[;;]]','[[;;]Foo]','[[;;]Bar]','[[;;]]']],668['Lorem[[;;]]Ipsum[[;;]Foo]Dolor[[;;]Bar]Sit[[;;]]Amet', [669'Lorem', '[[;;]]', 'Ipsum', '[[;;]Foo]', 'Dolor', '[[;;]Bar]', 'Sit', '[[;;]]', 'Amet'670]]671];672it('should split text inot formatting', function() {673input.forEach(function(spec) {674expect($.terminal.format_split(spec[0])).toEqual(spec[1]);675});676});677});678describe('$.terminal.substring', function() {679var input = '[[;;]Lorem ipsum dolor sit amet], [[;;]consectetur adipiscing elit]. [[;;]Maecenas ac massa tellus. Sed ac feugiat leo].';680it('should return substring when starting at 0', function() {681var tests = [682[25, '[[;;]Lorem ipsum dolor sit ame]'],683[26, '[[;;]Lorem ipsum dolor sit amet]'],684[27, '[[;;]Lorem ipsum dolor sit amet],'],685[30, '[[;;]Lorem ipsum dolor sit amet], [[;;]co]']686];687tests.forEach(function(spec) {688expect($.terminal.substring(input, 0, spec[0])).toEqual(spec[1]);689});690});691it('should split text into one characters', function() {692var string = 'Lorem ipsum dolor sit amet';693var input = '[[;;]' + string + ']';694var len = $.terminal.length(input);695for (var i = 0; i < len; ++i) {696var output = $.terminal.substring(input, i, i + 1);697expect($.terminal.is_formatting(output)).toBe(true);698expect($.terminal.length(output)).toBe(1);699expect($.terminal.strip(output)).toEqual(string.substring(i, i + 1));700}701// case for issue #550702expect($.terminal.substring(input, i, i + 1)).toEqual('');703});704it('should create formatting for each character', function() {705var formatting = '[[b;;;token number]10][[b;;;token operator]+][[b;;;token number]10]';706707var len = $.terminal.strip(formatting).length;708var result = [];709for (var i = 0; i < len; ++i) {710result.push($.terminal.substring(formatting, i,i+1));711}712expect(result).toEqual([713'[[b;;;token number]1]',714'[[b;;;token number]0]',715'[[b;;;token operator]+]',716'[[b;;;token number]1]',717'[[b;;;token number]0]'718]);719});720it('should return substring when ending at length or larger', function() {721var tests = [722[0, '[[;;]Lorem ipsum dolor sit amet], [[;;]consectetur adipiscing elit]. [[;;]Maecenas ac massa tellus. Sed ac feugiat leo].'],723[10, '[[;;]m dolor sit amet], [[;;]consectetur adipiscing elit]. [[;;]Maecenas ac massa tellus. Sed ac feugiat leo].'],724[27, ' [[;;]consectetur adipiscing elit]. [[;;]Maecenas ac massa tellus. Sed ac feugiat leo].'],725[30, '[[;;]nsectetur adipiscing elit]. [[;;]Maecenas ac massa tellus. Sed ac feugiat leo].']726];727tests.forEach(function(spec) {728expect($.terminal.substring(input, spec[0], 102)).toEqual(spec[1]);729expect($.terminal.substring(input, spec[0], 200)).toEqual(spec[1]);730});731});732it('should return substring when input starts from normal text', function() {733var input = 'Lorem Ipsum [[;;]Dolor]';734expect($.terminal.substring(input, 10, 200)).toEqual('m [[;;]Dolor]');735});736it('should substring when string have no formatting', function() {737var input = 'Lorem Ipsum Dolor Sit Amet';738var tests = [739[0, 10, 'Lorem Ipsu'],740[10, 20, 'm Dolor Si'],741[20, 27, 't Amet']742];743tests.forEach(function(spec) {744expect($.terminal.substring(input, spec[0], spec[1])).toEqual(spec[2]);745});746});747});748describe('$.terminal.normalize', function() {749function test(specs) {750specs.forEach(function(spec) {751expect($.terminal.normalize(spec[0])).toEqual(spec[1]);752});753}754it('should add 5 argument to formatting', function() {755var tests = [756['[[;;]Lorem] [[;;]Ipsum] [[;;;]Dolor]', '[[;;;;Lorem]Lorem] [[;;;;Ipsum]Ipsum] [[;;;;Dolor]Dolor]'],757['[[;;;;]Lorem Ipsum Dolor] [[;;;;]Amet]', '[[;;;;Lorem Ipsum Dolor]Lorem Ipsum Dolor] [[;;;;Amet]Amet]']758];759test(tests);760});761it('should not add 5 argument', function() {762var tests = [763[764'[[;;;;Foo]Lorem Ipsum Dolor] [[;;;;Bar]Amet]',765'[[;;;;Foo]Lorem Ipsum Dolor] [[;;;;Bar]Amet]']766];767test(tests);768});769it('should remove empty formatting', function() {770var tests = [771[772'[[;;]]Lorem Ipsum [[;;]]Dolor Sit [[;;;;]]Amet',773'Lorem Ipsum Dolor Sit Amet'774]775];776test(tests);777});778it('should not change formatting', function() {779var tests = [780'[[;;]Lorem] [[;;]Ipsum] [[;;;]Dolor]',781'[[;;;;]Lorem Ipsum Dolor] [[;;;;]Amet]',782'[[;;;;Lorem Ipsum Dolor]Lorem Ipsum Dolor] [[;;;;Amet]Amet]',783'[[;;]]Lorem Ipsum [[;;]]Dolor Sit [[;;;;]]Amet',784'Lorem Ipsum Dolor Sit Amet'785].forEach(function(string) {786var normalized = $.terminal.normalize(string);787expect($.terminal.normalize(normalized)).toEqual(normalized);788});789});790});791describe('$.terminal.is_formatting', function() {792it('should detect terminal formatting', function() {793var formattings = [794'[[;;]Te[xt]',795'[[;;]Te\\]xt]',796'[[;;]]',797'[[gui;;;class]Text]',798'[[b;#fff;]Text]',799'[[b;red;blue]Text]'];800var not_formattings = [801'[[;;]Text[',802'[[Text]]',803'[[Text[[',804'[[;]Text]',805'Text]',806'[[Text',807'[;;]Text]'];808formattings.forEach(function(formatting) {809expect($.terminal.is_formatting(formatting)).toEqual(true);810});811not_formattings.forEach(function(formatting) {812expect($.terminal.is_formatting(formatting)).toEqual(false);813});814});815});816describe('$.terminal.escape_regex', function() {817it('should escape regex special characters', function() {818var safe = "\\\\\\^\\*\\+\\?\\.\\$\\[\\]\\{\\}\\(\\)";819expect($.terminal.escape_regex('\\^*+?.$[]{}()')).toEqual(safe);820});821});822describe('$.terminal.have_formatting', function() {823var formattings = [824'some text [[;;]Te[xt] and formatting',825'some text [[;;]Te\\]xt] and formatting',826'some text [[;;]] and formatting',827'some text [[gui;;;class]Text] and formatting',828'some text [[b;#fff;]Text] and formatting',829'some text [[b;red;blue]Text] and formatting'];830var not_formattings = [831'some text [[;;]Text[ and formatting',832'some text [[Text]] and formatting',833'some text [[Text[[ and formatting',834'some text [[;]Text] and formatting',835'some text Text] and formatting',836'some text [[Text and formatting',837'some text [;;]Text] and formatting'];838it('should detect terminal formatting', function() {839formattings.forEach(function(formatting) {840expect($.terminal.have_formatting(formatting)).toEqual(true);841});842not_formattings.forEach(function(formatting) {843expect($.terminal.have_formatting(formatting)).toEqual(false);844});845});846});847describe('$.terminal.valid_color', function() {848it('should mark hex color as valid', function() {849var valid_colors = ['#fff', '#fab', '#ffaacc', 'red', 'blue'];850valid_colors.forEach(function(color) {851expect($.terminal.valid_color(color)).toBe(true);852});853});854});855describe('$.terminal.format', function() {856var format = '[[biugs;#fff;#000]Foo][[i;;;foo]Bar][[ous;;]Baz]';857it('should create html span tags with style and classes', function() {858var string = $.terminal.format(format);859expect(string).toEqual('<span style="font-weight:bold;text-decorat'+860'ion:underline line-through;font-style:ital'+861'ic;color:#fff;--color:#fff;text-shadow:0 0'+862' 5px #fff;background-color:#000;" data-tex'+863't="Foo">Foo</span><span style="font-style:'+864'italic;" class="foo" data-text="Bar">Bar</'+865'span><span style="text-decoration:underlin'+866'e line-through overline;" data-text="Baz">'+867'Baz</span>');868});869it('should escape brackets', function() {870var specs = [871['\\]', ']'],872['\\]xxx', ']xxx'],873['xxx\\]xxx', 'xxx]xxx'],874['xxx\\]', 'xxx]'],875['[[;;]\\]xxx]', ']xxx'],876['[[;;]xxx\\]]', 'xxx]'],877['[[;;]\\]]', ']'],878['[[;;]xxx\\]xxx]', 'xxx]xxx']879];880specs.forEach(function(spec) {881var output = $.terminal.format(spec[0]);882expect($('<div>' + output + '</div>').text()).toEqual(spec[1]);883});884});885it('should handle wider characters without formatting', function() {886var input = 'ターミナルウィンドウは黒[[;;]です]';887var string = $.terminal.format(input, {char_width: 7});888function wrap(str) {889return str.split('').map(char => {890return '<span style="width: 2ch">' + char + '</span>';891}).join('');892}893var chars_a = wrap('ターミナルウィンドウは黒').split('').map(x => {894return '<span style="width: 2ch">' + x + '</span>';895}).join('');896expect(string).toEqual('<span style="width: 24ch"><span style="widt'+897'h: 24ch">' + wrap('ターミナルウィンドウは黒') +898'</span></span><span style="width: 4ch" data'+899'-text="です"><span style="width: 4ch">' +900wrap('です') + '</span></span>');901});902it('should handle links', function() {903var input = '[[!;;]https://terminal.jcubic.pl]';904var tests = [905[906'<a target="_blank" href="https://terminal.jcubic.pl"'+907' rel="noopener" tabindex="1000" data-text>https:'+908'//terminal.jcubic.pl</a>',909{}910],911[912'<a target="_blank" href="https://terminal.jcubic.pl"'+913' rel="noreferrer noopener" tabindex="1000" data-'+914'text>https://terminal.jcubic.pl</a>',915{916linksNoReferrer: true917}918]919];920tests.forEach(function(spec) {921var expected = spec[0];922var options = spec[1];923var output = $.terminal.format(input, spec[1]);924expect(output).toEqual(expected);925});926});927it('should handle javascript links', function() {928var js = "javascript".split('').map(function(chr) {929return '&#' + chr.charCodeAt(0) + ';';930}).join('');931var tests = [932[933"[[!;;;;javascript:alert('x')]xss]", {},934'<a target="_blank" rel="noopener"' +935' tabindex="1000" data-text>xss</a>'936],937[938"[[!;;;;javascript:alert('x')]xss]", {anyLinks: true},939'<a target="_blank" href="javascript:alert(\'x\')"' +940' rel="noopener" tabindex="1000" data-text>xss</a>'941],942[943"[[!;;;;" + js + ":alert('x')]xss]", {},944'<a target="_blank" rel="noopener"' +945' tabindex="1000" data-text>xss</a>'946],947[948"[[!;;;;JaVaScRiPt:alert('x')]xss]", {anyLinks: false},949'<a target="_blank" rel="noopener"' +950' tabindex="1000" data-text>xss</a>'951],952];953tests.forEach(function(spec) {954var output = $.terminal.format(spec[0], spec[1]);955expect(output).toEqual(spec[2]);956});957});958it('should add nofollow', function() {959var input = '[[!;;]https://terminal.jcubic.pl]';960var tests = [961[962'<a target="_blank" href="https://terminal.jcubic.pl"'+963' rel="nofollow noopener" tabindex="1000" data-te'+964'xt>https://terminal.jcubic.pl</a>',965{linksNoFollow: true}966],967[968'<a target="_blank" href="https://terminal.jcubic.pl"'+969' rel="nofollow noreferrer noopener" tabindex="1000"'+970' data-text>https://terminal.jcubic.pl</a>',971{972linksNoReferrer: true,973linksNoFollow: true974}975]976];977tests.forEach(function(spec) {978var expected = spec[0];979var options = spec[1];980var output = $.terminal.format(input, spec[1]);981expect(output).toEqual(expected);982});983});984it('should handle emails', function() {985var tests = [986[987'[[!;;][email protected]]',988'<a href="mailto:[email protected]" tabindex="1000" data-text>[email protected]</a>'989],990[991'[[!;;;;[email protected]]j][[!;;;;[email protected]][email protected]]',992'<a href="mailto:[email protected]" tabindex="1000" data-text>j</a>' +993'<a href="mailto:[email protected]" tabindex="1000" data-text>c' +994'[email protected]</a>'995]996];997tests.forEach(function([input, expected]) {998expect($.terminal.format(input)).toEqual(expected);999});1000});1001it('should skip empty parts', function() {1002var input = '[[;;]]x[[b;;]y][[b;;]z]';1003var output = $.terminal.format(input);1004expect(output).toEqual('<span>x</span><span style="font-weight:' +1005'bold;" data-text="y">y</span><span styl' +1006'e="font-weight:bold;" data-text="z">z</span>');1007});1008it('should handle JSON', function() {1009var input = '[[;;;;;{"title": "foo", "data-foo": "bar"}]foo]';1010var output = $.terminal.format(input, {1011allowedAttributes: [/^data-/, 'title']1012});1013expect(output).toEqual('<span title="foo" data-foo="bar" data-text=' +1014'"foo">foo</span>');1015});1016it('should not allow attributes', function() {1017var input = '[[;;;;;{"title": "foo", "data-foo": "bar"}]foo]';1018var output = $.terminal.format(input, {1019allowedAttributes: []1020});1021expect(output).toEqual('<span data-text="foo">foo</span>');1022});1023it('should filter out attribute in JSON', function() {1024var input = '[[;;;;;{"title": "foo", "data-foo": "bar"}]foo]';1025var output = $.terminal.format(input, {1026allowedAttributes: ['title']1027});1028expect(output).toEqual('<span title="foo" data-text=' +1029'"foo">foo</span>');1030});1031it('should parse JSON if semicolon in value', function() {1032var input = '[[;;;;;{"title": "foo ; bar"}]foo]';1033var output = $.terminal.format(input, {1034allowedAttributes: ['title']1035});1036expect(output).toEqual('<span title="foo ; bar" data-text=' +1037'"foo">foo</span>');1038});1039it("should not duplicate and don't overwrite data-text", function() {1040var input = '[[;;;;;{"data-text": "bar"}]foo]';1041var output = $.terminal.format(input, {1042allowedAttributes: []1043});1044expect(output).toEqual('<span data-text="foo">foo</span>');1045output = $.terminal.format(input, {1046allowedAttributes: ['data-text']1047});1048expect(output).toEqual('<span data-text="foo">foo</span>');1049});1050});1051describe('$.terminal.strip', function() {1052it('should remove formatting', function() {1053var formatting = '-_-[[biugs;#fff;#000]Foo]-_-[[i;;;foo]Bar]-_-[[ous;;'+1054']Baz]-_-';1055var result = '-_-Foo-_-Bar-_-Baz-_-';1056expect($.terminal.strip(formatting)).toEqual(result);1057});1058it('should remove escaping brackets from string', function() {1059var formatting = 'foo [[;;]bar\\]baz]';1060var result = 'foo bar]baz';1061expect($.terminal.strip(formatting)).toEqual(result);1062});1063});1064describe('$.terminal.apply_formatters', function() {1065var formatters;1066beforeEach(function() {1067formatters = $.terminal.defaults.formatters.slice();1068});1069afterEach(function() {1070$.terminal.defaults.formatters = formatters;1071});1072it('should apply function formatters', function() {1073$.terminal.defaults.formatters = [1074function(str) {1075return str.replace(/a/g, '[[;;;A]a]');1076},1077function(str) {1078return str.replace(/b/g, '[[;;;B]b]');1079}1080];1081var input = 'aaa bbb';1082var output = '[[;;;A]a][[;;;A]a][[;;;A]a] [[;;;B]b][[;;;B]b][[;;;B]b]';1083expect($.terminal.apply_formatters(input)).toEqual(output);1084});1085it('should apply __meta__ and array formatter', function() {1086var input = 'lorem ipsum';1087var output = '[[;;]lorem] ipsum';1088var test = {1089formatter: function(string) {1090expect(string).toEqual(output);1091return string.replace(/ipsum/g, '[[;;]ipsum]');1092}1093};1094spy(test, 'formatter');1095test.formatter.__meta__ = true;1096$.terminal.defaults.formatters = [[/lorem/, '[[;;]lorem]'], test.formatter];1097expect($.terminal.apply_formatters(input)).toEqual('[[;;]lorem] [[;;]ipsum]');1098expect(test.formatter).toHaveBeenCalled();1099});1100it('should throw except', function() {1101var formatters = $.terminal.defaults.formatters.slice();1102$.terminal.defaults.formatters.push(function() {1103x();1104});1105expect(function() {1106$.terminal.apply_formatters('foo');1107}).toThrow($.terminal.Exception('Error in formatter [' +1108(formatters.length - 1) +1109']'));1110$.terminal.defaults.formatters.pop();1111});1112it('should process in a loop', function() {1113$.terminal.defaults.formatters.push([/(^|x)[0-9]/, '$1x', {loop: true}]);1114var input = '00000000000000000000000000';1115var output = $.terminal.apply_formatters(input);1116expect(output).toEqual(input.replace(/0/g, 'x'));1117});1118});1119describe('$.terminal.split_equal', function() {1120var text = ['[[bui;#fff;]Lorem ipsum dolor sit amet, consectetur adipi',1121'scing elit. Nulla sed dolor nisl, in suscipit justo. Donec a enim',1122' et est porttitor semper at vitae augue. Proin at nulla at dui ma',1123'ttis mattis. Nam a volutpat ante. Aliquam consequat dui eu sem co',1124'nvallis ullamcorper. Nulla suscipit, massa vitae suscipit ornare,',1125' tellus] est [[b;;#f00]consequat nunc, quis blandit elit odio eu ',1126'arcu. Nam a urna nec nisl varius sodales. Mauris iaculis tincidun',1127't orci id commodo. Aliquam] non magna quis [[i;;]tortor malesuada',1128' aliquam] eget ut lacus. Nam ut vestibulum est. Praesent volutpat',1129' tellus in eros dapibus elementum. Nam laoreet risus non nulla mo',1130'llis ac luctus [[ub;#fff;]felis dapibus. Pellentesque mattis elem',1131'entum augue non sollicitudin. Nullam lobortis fermentum elit ac m',1132'ollis. Nam ac varius risus. Cras faucibus euismod nulla, ac aucto',1133'r diam rutrum sit amet. Nulla vel odio erat], ac mattis enim.'1134].join('');1135it('should keep formatting if it span across multiple lines', function() {1136var array = ["[[bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adipisc"+1137"ing elit. Nulla sed dolor nisl, in suscipit justo. Donec a e"+1138"nim et est porttitor semper at vitae augue. Proin at nulla a"+1139"t dui mattis mattis. Nam a volutpat ante. Aliquam consequat "+1140"dui eu sem convallis ullamcorper. Nulla suscipit, massa vita"+1141"e suscipit ornare, tellus]Lorem ipsum dolor sit amet, consec"+1142"tetur adipiscing elit. Nulla sed dolor nisl, in suscipit jus"+1143"to. Do]","[[bui;#fff;;;Lorem ipsum dolor sit amet, consectet"+1144"ur adipiscing elit. Nulla sed dolor nisl, in suscipit justo."+1145" Donec a enim et est porttitor semper at vitae augue. Proin "+1146"at nulla at dui mattis mattis. Nam a volutpat ante. Aliquam "+1147"consequat dui eu sem convallis ullamcorper. Nulla suscipit, "+1148"massa vitae suscipit ornare, tellus]nec a enim et est portti"+1149"tor semper at vitae augue. Proin at nulla at dui mattis matt"+1150"is. Nam a volutp]","[[bui;#fff;;;Lorem ipsum dolor sit amet,"+1151" consectetur adipiscing elit. Nulla sed dolor nisl, in susci"+1152"pit justo. Donec a enim et est porttitor semper at vitae aug"+1153"ue. Proin at nulla at dui mattis mattis. Nam a volutpat ante"+1154". Aliquam consequat dui eu sem convallis ullamcorper. Nulla "+1155"suscipit, massa vitae suscipit ornare, tellus]at ante. Aliqu"+1156"am consequat dui eu sem convallis ullamcorper. Nulla suscipi"+1157"t, massa vitae suscipit or]","[[bui;#fff;;;Lorem ipsum dolor"+1158" sit amet, consectetur adipiscing elit. Nulla sed dolor nisl"+1159", in suscipit justo. Donec a enim et est porttitor semper at"+1160" vitae augue. Proin at nulla at dui mattis mattis. Nam a vol"+1161"utpat ante. Aliquam consequat dui eu sem convallis ullamcorp"+1162"er. Nulla suscipit, massa vitae suscipit ornare, tellus]nare"+1163", tellus] est [[b;;#f00;;consequat nunc, quis blandit elit o"+1164"dio eu arcu. Nam a urna nec nisl varius sodales. Mauris iacu"+1165"lis tincidunt orci id commodo. Aliquam]consequat nunc, quis "+1166"blandit elit odio eu arcu. Nam a urna nec nisl varius sodale"+1167"s.]","[[b;;#f00;;consequat nunc, quis blandit elit odio eu a"+1168"rcu. Nam a urna nec nisl varius sodales. Mauris iaculis tinc"+1169"idunt orci id commodo. Aliquam] Mauris iaculis tincidunt orc"+1170"i id commodo. Aliquam] non magna quis [[i;;;;tortor malesuad"+1171"a aliquam]tortor malesuada aliquam] eget ut l","acus. Nam ut"+1172" vestibulum est. Praesent volutpat tellus in eros dapibus el"+1173"ementum. Nam laoreet risus n","on nulla mollis ac luctus [[u"+1174"b;#fff;;;felis dapibus. Pellentesque mattis elementum augue "+1175"non sollicitudin. Nullam lobortis fermentum elit ac mollis. "+1176"Nam ac varius risus. Cras faucibus euismod nulla, ac auctor "+1177"diam rutrum sit amet. Nulla vel odio erat]felis dapibus. Pel"+1178"lentesque mattis elementum augue non sollicitudin. Nulla]",1179"[[ub;#fff;;;felis dapibus. Pellentesque mattis elementum aug"+1180"ue non sollicitudin. Nullam lobortis fermentum elit ac molli"+1181"s. Nam ac varius risus. Cras faucibus euismod nulla, ac auct"+1182"or diam rutrum sit amet. Nulla vel odio erat]m lobortis ferm"+1183"entum elit ac mollis. Nam ac varius risus. Cras faucibus eui"+1184"smod nulla, ac auctor dia]","[[ub;#fff;;;felis dapibus. Pell"+1185"entesque mattis elementum augue non sollicitudin. Nullam lob"+1186"ortis fermentum elit ac mollis. Nam ac varius risus. Cras fa"+1187"ucibus euismod nulla, ac auctor diam rutrum sit amet. Nulla "+1188"vel odio erat]m rutrum sit amet. Nulla vel odio erat], ac ma"+1189"ttis enim."];1190$.terminal.split_equal(text, 100).forEach(function(line, i) {1191expect(line).toEqual(array[i]);1192});1193});1194it("should keep formatting if span across line with newline characters", function() {1195var text = ['[[bui;#fff;]Lorem ipsum dolor sit amet, consectetur adipi',1196'scing elit. Nulla sed dolor nisl, in suscipit justo. Donec a enim',1197' et est porttitor semper at vitae augue. Proin at nulla at dui ma',1198'ttis mattis. Nam a volutpat ante. Aliquam consequat dui eu sem co',1199'nvallis ullamcorper. Nulla suscipit, massa vitae suscipit ornare,',1200' tellus]'].join('\n');1201var formatting = /^\[\[bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adipi\\nscing elit. Nulla sed dolor nisl, in suscipit justo. Donec a enim\\n et est porttitor semper at vitae augue. Proin at nulla at dui ma\\nttis mattis. Nam a volutpat ante. Aliquam consequat dui eu sem co\\nnvallis ullamcorper. Nulla suscipit, massa vitae suscipit ornare,\\n tellus\]/;1202$.terminal.split_equal(text, 100).forEach(function(line, i) {1203if (!line.match(formatting)) {1204throw new Error("Line nr " + i + " " + line + " don't have correct " +1205"formatting");1206}1207});1208});1209function test_lenghts(string, fn) {1210var cols = [10, 40, 50, 60, 400];1211for (var i=cols.length; i--;) {1212var lines = $.terminal.split_equal(string, cols[i]);1213var max_len;1214var lengths;1215if (fn) {1216lengths = lines.map(function(line) {1217return fn(line).length;1218});1219max_len = fn(string).length;1220} else {1221lengths = lines.map(function(line) {1222return line.length;1223});1224max_len = fn.length;1225}1226lengths.forEach(function(length, j) {1227var max = max_len < cols[i] ? max_len : cols[i];1228if (j < lengths - 1) {1229if (length != max) {1230throw new Error('Lines count is ' + JSON.stringify(lengths) +1231' but it should have ' + cols[i] +1232' line ' + JSON.stringify(lines[j]));1233}1234} else {1235if (length > max) {1236throw new Error('Lines count is ' + JSON.stringify(lengths) +1237' but it should have ' + cols[i] +1238' line ' + JSON.stringify(lines[j]));1239}1240}1241});1242expect(true).toEqual(true);1243}1244}1245it('should split text into equal length chunks', function() {1246test_lenghts(text, function(line) {1247return $.terminal.strip(line);1248});1249});1250it('should split text when all brackets are escaped', function() {1251test_lenghts($.terminal.escape_brackets(text), function(line) {1252return $('<div>' + line + '</div>').text();1253});1254});1255it('should return whole lines if length > then the length of the line', function() {1256var test = [1257{1258input: ['[[bui;#fff;]Lorem ipsum dolor sit amet,] consectetur adipi',1259'scing elit.'].join(''),1260output: [['[[bui;#fff;;;Lorem ipsum dolor sit amet,]Lorem ipsum dol',1261'or sit amet,] consectetur adipiscing elit.'].join('')]1262},1263{1264input: ['[[bui;#fff;]Lorem ipsum dolor sit amet, consectetur adipi',1265'scing elit.]'].join(''),1266output: [[1267'[[bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adipi',1268'scing elit.]Lorem ipsum dolor sit amet, consectetur adipis',1269'cing elit.]'].join('')]1270},1271{1272input: ['[[bui;#fff;]Lorem ipsum dolor sit amet, consectetur adipi',1273'scing elit.]\n[[bui;#fff;]Lorem ipsum dolor sit amet, con',1274'sectetur adipiscing elit.]'].join(''),1275output: [1276[1277'[[bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adipi',1278'scing elit.]Lorem ipsum dolor sit amet, consectetur adipis',1279'cing elit.]'].join(''),1280['[[bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adipi',1281'scing elit.]Lorem ipsum dolor sit amet, consectetur adipis',1282'cing elit.]'].join('')]1283},1284{1285input: ['[[bui;#fff;]Lorem ipsum dolor sit amet, consectetur adipi',1286'scing elit.]\n[[bui;#fff;]Lorem ipsum dolor sit amet, con',1287'sectetur adipiscing elit.]\n[[bui;#fff;]Lorem ipsum dolor',1288' sit amet, consectetur adipiscing elit.]\n[[bui;#fff;]Lor',1289'em ipsum dolor sit amet, consectetur adipiscing elit.]'1290].join(''),1291output: ['[[bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adi'+1292'piscing elit.]Lorem ipsum dolor si]','[[bui;#fff;;;Lorem'+1293' ipsum dolor sit amet, consectetur adipiscing elit.]t am'+1294'et, consectetur ]','[[bui;#fff;;;Lorem ipsum dolor sit a'+1295'met, consectetur adipiscing elit.]adipiscing elit.]','[['+1296'bui;#fff;;;Lorem ipsum dolor sit amet, consectetur adipi'+1297'scing elit.]Lorem ipsum dolor si]','[[bui;#fff;;;Lorem i'+1298'psum dolor sit amet, consectetur adipiscing elit.]t amet'+1299', consectetur ]','[[bui;#fff;;;Lorem ipsum dolor sit ame'+1300't, consectetur adipiscing elit.]adipiscing elit.]','[[bu'+1301'i;#fff;;;Lorem ipsum dolor sit amet, consectetur adipisc'+1302'ing elit.]Lorem ipsum dolor si]','[[bui;#fff;;;Lorem ips'+1303'um dolor sit amet, consectetur adipiscing elit.]t amet, '+1304'consectetur ]','[[bui;#fff;;;Lorem ipsum dolor sit amet,'+1305' consectetur adipiscing elit.]adipiscing elit.]','[[bui;'+1306'#fff;;;Lorem ipsum dolor sit amet, consectetur adipiscin'+1307'g elit.]Lorem ipsum dolor si]','[[bui;#fff;;;Lorem ipsum'+1308' dolor sit amet, consectetur adipiscing elit.]t amet, co'+1309'nsectetur ]','[[bui;#fff;;;Lorem ipsum dolor sit amet, c'+1310'onsectetur adipiscing elit.]adipiscing elit.]'],1311split: 201312}1313];1314test.forEach(function(test) {1315var array = $.terminal.split_equal(test.input, test.split || 100);1316expect(array).toEqual(test.output);1317});1318});1319it('should handle new line as first character of formatting #375', function() {1320var specs = [1321['A[[;;]\n]B', ['A', '[[;;;;\\n]]B']],1322['A[[;;]\nB]C', ['A', '[[;;;;\\nB]B]C']]1323];1324specs.forEach(function(spec) {1325expect($.terminal.split_equal(spec[0])).toEqual(spec[1]);1326});1327});1328it('should handle wider characters', function() {1329var input = 'ターミナルウィンドウは黒です';1330var count = 0;1331var len = 0;1332$.terminal.split_equal(input, 4).forEach(function(string) {1333var width = wcwidth(string);1334expect(width).toEqual(4);1335expect(string.length).toEqual(2);1336len += string.length;1337count += width;1338});1339expect(wcwidth(input)).toEqual(count);1340expect(input.length).toEqual(len);1341});1342function test_codepoints(input) {1343var length = input.length;1344for (var i = 2; i < 10; i++) {1345var len = 0;1346var count = 0;1347$.terminal.split_equal(input, i).forEach(function(string) {1348len += string.length;1349var width = wcwidth(string);1350if (len < length) {1351expect(width).toEqual(i);1352} else {1353expect(width <= i).toBeTruthy();1354}1355count += width;1356});1357expect([i, input, len]).toEqual([i, input, length]);1358expect([i, input, count]).toEqual([i, input, wcwidth(input)]);1359}1360}1361it('should handle emoji', function() {1362var input = [1363"\u263a\ufe0f xxxx \u261d\ufe0f xxxx \u0038\ufe0f\u20e3 xxx\u0038\ufe0f\u20e3",1364"\u263a\ufe0f xxxx \u261d\ufe0f x \u0038\ufe0f\u20e3 xxx\u0038\ufe0f\u20e3"1365];1366input.forEach(test_codepoints);1367});1368it('should handle combine characters', function() {1369var input = [1370's\u030A\u032A xxxx s\u030A\u032A xxxx s\u030A\u032A xxxx',1371's\u030A\u032A xxxx s\u030A\u032A xxxx s\u030A\u032A xxxs\u030A\u032A'1372];1373input.forEach(test_codepoints);1374});1375it('should handle mixed size characters', function() {1376var input = 'ターミナルウィンドウは黒です lorem ipsum';1377var given = $.terminal.split_equal(input, 10);1378given.forEach(function(string) {1379expect(string.length).toBeLessThan(11);1380expect(wcwidth(string)).toBeLessThan(11);1381});1382var expected = ["ターミナル", "ウィンドウ", "は黒です l", "orem ipsum"];1383expect(given).toEqual(expected);1384});1385it('should split normal text with brackets', function() {1386var text = 'jcubic@gitwebterm:/git [gh-pages] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';1387test_lenghts(text, function(line) {1388return $('<div>' + line + '</div>').text();1389});1390var output = $.terminal.split_equal(text, 50);1391output.forEach(function(string) {1392expect(string.length - 1).toBeLessThan(50);1393});1394});1395it('should split on full formatting with multiple lines', function() {1396var text = '[[;;]foo\nbar]';1397var output = $.terminal.split_equal(text, 50);1398expect(output).toEqual(['[[;;;;foo\\nbar]foo]', '[[;;;;foo\\nbar]bar]']);1399});1400it('should not split on words of full formatting when text have less length', function() {1401var text = '[[;red;]xxx xx xx]';1402var output = $.terminal.split_equal(text, 100, true);1403expect(output).toEqual([$.terminal.normalize(text)]);1404});1405});1406describe('Cycle', function() {1407describe('create', function() {1408it('should create Cycle from init values', function() {1409var cycle = new $.terminal.Cycle(1, 2, 3);1410expect(cycle.get()).toEqual([1, 2, 3]);1411});1412it('should create empty Cycle', function() {1413var cycle = new $.terminal.Cycle();1414expect(cycle.get()).toEqual([]);1415});1416it('should start at the begining when called init data', function() {1417var cycle = new $.terminal.Cycle(1, 2, 3);1418expect(cycle.index()).toEqual(0);1419expect(cycle.front()).toEqual(1);1420});1421it('should start at the begining when called without data', function() {1422var cycle = new $.terminal.Cycle();1423expect(cycle.index()).toEqual(0);1424expect(cycle.front()).toEqual(undefined);1425});1426});1427describe('index', function() {1428var a = {a: 1};1429var b = {a: 2};1430var c = {a: 3};1431var d = {a: 4};1432var cycle;1433beforeEach(function() {1434cycle = new $.terminal.Cycle(a, b, c, d);1435});1436it('should return index', function() {1437expect(cycle.index()).toEqual(0);1438cycle.rotate();1439expect(cycle.index()).toEqual(1);1440});1441it('should skip index if element removed', function() {1442cycle.remove(1);1443expect(cycle.index()).toEqual(0);1444cycle.rotate();1445expect(cycle.index()).toEqual(2);1446});1447});1448describe('rotate', function() {1449var a = {a: 1};1450var b = {a: 2};1451var c = {a: 3};1452var d = {a: 4};1453var cycle;1454beforeEach(function() {1455cycle = new $.terminal.Cycle(a, b, c, d);1456});1457it('should rotate to next element', function() {1458var object = cycle.rotate();1459expect(object).toEqual({a:2});1460expect(cycle.index()).toEqual(1);1461expect(cycle.front()).toEqual({a:2});1462});1463it('should rotate to next if item removed', function() {1464cycle.remove(1);1465var object = cycle.rotate();1466expect(object).toEqual({a:3});1467expect(cycle.index()).toEqual(2);1468expect(cycle.front()).toEqual({a:3});1469});1470it('should rotate to first if last is selected', function() {1471for (var i = 0; i < 3; ++i) {1472cycle.rotate();1473}1474var object = cycle.rotate();1475expect(object).toEqual({a:1});1476expect(cycle.index()).toEqual(0);1477expect(cycle.front()).toEqual({a:1});1478});1479});1480describe('set', function() {1481var a = {a: 1};1482var b = {a: 2};1483var c = {a: 3};1484var d = {a: 4};1485var cycle;1486beforeEach(function() {1487cycle = new $.terminal.Cycle(a, b, c, d);1488});1489it('should set existing element', function() {1490cycle.set(c);1491expect(cycle.front()).toEqual(c);1492});1493it('should add new item if not exists', function() {1494var e = {a: 5};1495cycle.set(e);1496expect(cycle.length()).toEqual(5);1497expect(cycle.index()).toEqual(4);1498expect(cycle.front()).toEqual(e);1499});1500});1501describe('map', function() {1502var a = {a: 1};1503var b = {a: 2};1504var c = {a: 3};1505var d = {a: 4};1506var cycle;1507beforeEach(function() {1508cycle = new $.terminal.Cycle(a, b, c, d);1509});1510it('should map over cycle', function() {1511var array = cycle.map(function(object) {1512return object.a;1513});1514expect(array).toEqual([1,2,3,4]);1515});1516it('should skip removed elements', function() {1517cycle.remove(1);1518cycle.remove(3);1519var array = cycle.map(function(object) {1520return object.a;1521});1522expect(array).toEqual([1,3]);1523});1524});1525describe('forEach', function() {1526var test;1527var a = {a: 1};1528var b = {a: 2};1529var c = {a: 3};1530var d = {a: 4};1531var cycle;1532beforeEach(function() {1533test = {1534test: function() {1535}1536};1537cycle = new $.terminal.Cycle(a, b, c, d);1538spy(test, 'test');1539});1540it('should execute callback for each item', function() {1541cycle.forEach(test.test);1542expect(count(test.test)).toBe(4);1543});1544it('should skip removed elements', function() {1545cycle.remove(1);1546cycle.forEach(test.test);1547expect(count(test.test)).toBe(3);1548});1549});1550describe('append', function() {1551it('should add element to cycle', function() {1552var cycle = new $.terminal.Cycle(1,2,3,4);1553cycle.append(5);1554expect(cycle.get()).toEqual([1,2,3,4,5]);1555});1556it('should add element to empty cycle', function() {1557var cycle = new $.terminal.Cycle();1558cycle.append(5);1559expect(cycle.get()).toEqual([5]);1560});1561it('should add element if cycle at the end', function() {1562var cycle = new $.terminal.Cycle(1,2,3);1563cycle.set(3);1564cycle.append(4);1565expect(cycle.get()).toEqual([1,2,3,4]);1566});1567});1568});1569describe('History', function() {1570function history_commands(name) {1571try {1572return JSON.parse(window.localStorage.getItem(name + '_commands'));1573} catch(e) {1574// to see in jest logs1575expect(window.localStorage.getItem(name)).toEqual('');1576}1577}1578function make_history(name, commands) {1579commands = commands || [];1580var history = new $.terminal.History('foo');1581commands.forEach(function(command) {1582history.append(command);1583});1584return history;1585}1586beforeEach(function() {1587window.localStorage.clear();1588});1589it('should create commands key', function() {1590var history = make_history('foo', ['item']);1591expect(Object.keys(window.localStorage)).toEqual(['foo_commands']);1592});1593it('should put items to localStorage', function() {1594var commands = ['lorem', 'ipsum'];1595var history = make_history('foo', commands);1596expect(history_commands('foo')).toEqual(commands);1597});1598it('should add only one commands if adding the same command', function() {1599var history = new $.terminal.History('foo');1600for (var i = 0; i < 10; ++i) {1601history.append('command');1602}1603expect(history_commands('foo')).toEqual(['command']);1604});1605it('shound not add more commands then the limit', function() {1606var history = new $.terminal.History('foo', 30);1607for (var i = 0; i < 40; ++i) {1608history.append('command ' + i);1609}1610expect(history_commands('foo').length).toEqual(30);1611});1612it('should create commands in memory', function() {1613window.localStorage.removeItem('foo_commands');1614var history = new $.terminal.History('foo', 10, true);1615for (var i = 0; i < 40; ++i) {1616history.append('command ' + i);1617}1618var data = history.data();1619expect(data instanceof Array).toBeTruthy();1620expect(data.length).toEqual(10);1621expect(window.localStorage.getItem('foo_commands')).toBeFalsy();1622});1623it('should clear localStorage', function() {1624var history = new $.terminal.History('foo');1625for (var i = 0; i < 40; ++i) {1626history.append('command ' + i);1627}1628history.purge();1629expect(window.localStorage.getItem('foo_commands')).not.toBeDefined();1630});1631it('should iterate over commands', function() {1632var commands = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];1633var history = make_history('foo', commands);1634var i;1635for (i=commands.length; i--;) {1636expect(history.current()).toEqual(commands[i]);1637expect(history.previous()).toEqual(commands[i-1]);1638}1639for (i=0; i<commands.length; ++i) {1640expect(history.current()).toEqual(commands[i]);1641expect(history.next()).toEqual(commands[i+1]);1642}1643});1644it('should not add commands when disabled', function() {1645var commands = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];1646var history = make_history('foo', commands);1647history.disable();1648history.append('foo');1649history.enable();1650history.append('bar');1651expect(history_commands('foo')).toEqual(commands.concat(['bar']));1652});1653it('should return last item', function() {1654var commands = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];1655var history = make_history('foo', commands);1656expect(history.last()).toEqual('amet');1657});1658it('should return position', function() {1659var commands = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];1660var last_index = commands.length - 1;1661var history = make_history('foo', commands);1662expect(history.position()).toEqual(last_index);1663history.previous();1664expect(history.position()).toEqual(last_index - 1);1665history.previous();1666expect(history.position()).toEqual(last_index - 2);1667history.next();1668history.next();1669expect(history.position()).toEqual(last_index);1670});1671it('should set data', function() {1672var commands = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];1673var last_index = commands.length - 1;1674var history = make_history('foo', []);1675history.set(commands);1676expect(history_commands('foo')).toEqual(commands);1677});1678});1679describe('Stack', function() {1680describe('create', function() {1681it('should create stack from array', function() {1682var stack = new $.terminal.Stack([1, 2, 3]);1683expect(stack.data()).toEqual([1, 2, 3]);1684});1685it('should create empty stack', function() {1686var stack = new $.terminal.Stack();1687expect(stack.data()).toEqual([]);1688});1689it('should create stack from single element', function() {1690var stack = new $.terminal.Stack(100);1691expect(stack.data()).toEqual([100]);1692});1693});1694describe('map', function() {1695it('should map over data', function() {1696var stack = new $.terminal.Stack([1,2,3,4]);1697var result = stack.map(function(n) { return n + 1; });1698expect(result).toEqual([2,3,4,5]);1699});1700it('should return empty array if no data on Stack', function() {1701var stack = new $.terminal.Stack([]);1702var result = stack.map(function(n) { return n + 1; });1703expect(result).toEqual([]);1704});1705});1706describe('size', function() {1707it('should return size', function() {1708var stack = new $.terminal.Stack([1,2,3,4]);1709expect(stack.size()).toEqual(4);1710});1711it('should return 0 for empyt stack', function() {1712var stack = new $.terminal.Stack([]);1713expect(stack.size()).toEqual(0);1714});1715});1716describe('pop', function() {1717it('should remove one element from stack', function() {1718var stack = new $.terminal.Stack([1,2,3,4]);1719var value = stack.pop();1720expect(value).toEqual(4);1721expect(stack.data()).toEqual([1,2,3]);1722});1723it('should return null for last element', function() {1724var stack = new $.terminal.Stack([1,2,3,4]);1725for (var i = 0; i < 4; ++i) {1726stack.pop();1727}1728expect(stack.pop()).toEqual(null);1729expect(stack.data()).toEqual([]);1730});1731});1732describe('push', function() {1733it('should push into empty stack', function() {1734var stack = new $.terminal.Stack();1735stack.push(100);1736expect(stack.data()).toEqual([100]);1737});1738it('should push on top of stack', function() {1739var stack = new $.terminal.Stack([1,2,3]);1740stack.push(4);1741stack.push(5);1742expect(stack.data()).toEqual([1,2,3,4,5]);1743});1744});1745describe('top', function() {1746it('should return value for first element', function() {1747var stack = new $.terminal.Stack([1,2,3]);1748expect(stack.top()).toEqual(3);1749stack.push(10);1750expect(stack.top()).toEqual(10);1751stack.pop();1752expect(stack.top()).toEqual(3);1753});1754});1755describe('clone', function() {1756it('should clone stack', function() {1757var stack = new $.terminal.Stack([1,2,3]);1758var stack_clone = stack.clone();1759expect(stack).not.toBe(stack_clone);1760expect(stack.data()).toEqual(stack_clone.data());1761});1762it('should clone empty stack', function() {1763var stack = new $.terminal.Stack([]);1764var stack_clone = stack.clone();1765expect(stack).not.toBe(stack_clone);1766expect(stack_clone.data()).toEqual([]);1767});1768});1769});1770describe('$.terminal.columns', function() {1771var input = [1772'lorem', 'ipsum', 'dolor', 'sit', 'amet',1773'lorem', 'ipsum', 'dolor', 'sit', 'amet',1774'lorem', 'ipsum', 'dolor', 'sit', 'amet'1775];1776var output = $.terminal.columns(input, 60);1777expect(typeof output).toBe('string');1778var lines = output.split('\n');1779lines.forEach(function(line, i) {1780expect(line.split(/\s+/)).toEqual([1781'lorem', 'ipsum', 'dolor', 'sit', 'amet'1782]);1783});1784expect($.terminal.columns(input, 5)).toEqual(input.join('\n'));1785});1786describe('$.terminal.formatter', function() {1787var t = $.terminal.formatter;1788it('should split formatting', function() {1789expect('[[;;]foo]bar[[;;]baz]'.split(t))1790.toEqual([1791'[[;;]foo]',1792'bar',1793'[[;;]baz]'1794]);1795});1796it('should strip formatting', function() {1797expect('[[;;]foo]bar[[;;]baz]'.replace(t, '$6')).toEqual('foobarbaz');1798});1799it('should match formatting', function() {1800expect('[[;;]foo]'.match(t)).toBeTruthy();1801expect('[[ ]]]'.match(t)).toBeFalsy();1802});1803it('should find formatting index', function() {1804expect('[[;;]foo]'.search(t)).toEqual(0);1805expect('xxx[[;;]foo]'.search(t)).toEqual(3);1806expect('[[ [[;;]foo] [[;;]bar]'.search(t)).toEqual(3);1807});1808});1809describe('$.terminal.parse_options', function() {1810function test(spec) {1811expect($.terminal.parse_options(spec[0], spec[2])).toEqual(spec[1]);1812}1813it('should create object from string', function() {1814test(['--foo bar -aif foo', {1815_: [],1816foo: 'bar',1817a: true,1818i: true,1819f: 'foo'1820}]);1821});1822it('should create object from array', function() {1823test([['--foo', 'bar', '-aif', 'foo'], {1824_: [],1825foo: 'bar',1826a: true,1827i: true,1828f: 'foo'1829}]);1830});1831it('should create boolean option for double dash if arg is missing', function() {1832[1833[1834['--foo', '-aif', 'foo'], {1835_: [],1836foo: true,1837a: true,1838i: true,1839f: 'foo'1840}1841],1842[1843['--foo', '--bar', '-i'], {1844_: [],1845foo: true,1846bar: true,1847i: true1848}1849],1850[1851['--foo', '--bar', '-i', '-b'], {1852_: [],1853foo: true,1854bar: true,1855i: true,1856b: true1857}1858]1859].forEach(test);1860});1861it('should create booelan options if they are on the list of booleans', function() {1862test([1863['--foo', 'bar', '-i', 'baz'], {1864_: ["bar"],1865foo: true,1866i: 'baz'1867}, {1868boolean: ['foo']1869}1870]);1871test([1872['--foo', 'bar', '-i', 'baz'], {1873_: ["bar", "baz"],1874foo: true,1875i: true1876}, {1877boolean: ['foo', 'i']1878}1879]);1880});1881});1882describe('$.terminal.tracking_replace', function() {1883function test(spec) {1884spec = spec.slice();1885var result = spec.pop();1886expect($.terminal.tracking_replace.apply(null, spec)).toEqual(result);1887}1888function test_positions(str, re, replacement, positions) {1889var output = str.replace(re, replacement);1890positions.forEach(function(n, i) {1891test([str, re, replacement, i, [output, n]]);1892});1893}1894it('should replace single value', function() {1895test(['foo bar', /foo/, 'f', 0, ['f bar', 0]]);1896test(['foo foo', /foo/, 'f', 0, ['f foo', 0]]);1897test(['foo bar', /bar/, 'f', 0, ['foo f', 0]]);1898});1899it('should remove all values', function() {1900test(['foo foo foo', /foo/g, 'f', 0, ['f f f', 0]]);1901});1902it('should replace matched values', function() {1903test(['100 200 30', /([0-9]+)/g, '($1$$)', 0, ['(100$) (200$) (30$)', 0]]);1904test(['100 200 30', /([0-9]+)/, '($1$$)', 0, ['(100$) 200 30', 0]]);1905});1906it('should track position if replacement is shorter', function() {1907var positions = [0, 1, 1, 1, 2, 3, 4, 5];1908test_positions('foo bar baz', /foo/g, 'f', [0, 1, 1, 1, 2, 3, 4, 5]);1909test_positions('foo foo foo', /foo/g, 'f', [19100, 1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 51911]);1912});1913it('should track position if replacement is longer', function() {1914test_positions('foo bar baz', /f/g, 'bar', [19150, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 131916]);1917test_positions('foo foo foo', /f/g, 'bar', [19180, 3, 4, 5, 6, 9, 10, 11, 12, 15, 16, 171919]);1920});1921});1922describe('$.terminal.iterator', function() {1923var input = ["\u263a\ufe0f", "x", "x", "x" ,"x", "\u261d\ufe0f", "x", "x", "x", "x",1924"\u0038\ufe0f\u20e3", "x","x","x","\u0038\ufe0f\u20e3"];1925it('should work with for of', function() {1926var i = 0;1927for (let item of $.terminal.iterator(input.join(''))) {1928expect(item).toEqual(input[i++]);1929}1930expect(input).toEqual($.terminal.split_characters(input.join('')));1931});1932it('should work with iterator protocol', function() {1933var iterator = $.terminal.iterator(input.join(''))[Symbol.iterator]();1934var i = 0;1935while (true) {1936var item = iterator.next();1937if (item.done) {1938break;1939}1940expect(item.value).toEqual(input[i++]);1941}1942});1943it('should iterate over formatting', function() {1944var input = '[[;blue;]abc][[;red;]def]';1945var arr = [1946'[[;blue;]a]',1947'[[;blue;]b]',1948'[[;blue;]c]',1949'[[;red;]d]',1950'[[;red;]e]',1951'[[;red;]f]'1952];1953var i = 0;1954for (var x of $.terminal.iterator(input)) {1955expect(x).toEqual(arr[i++]);1956}1957});1958it('should handle escape bracket', function() {1959var input = '[[;blue;]foo \\ bar]';1960var arr = 'foo \\ bar'.split('').map(x => '[[;blue;]' + (x === '\\' ? '\\\\' : x) + ']');1961var i = 0;1962for (var x of $.terminal.iterator('[[;blue;]foo \\ bar]')) {1963expect(x).toEqual(arr[i++]);1964}1965});1966});1967describe('$.terminal.new_formatter', function() {1968function nested_index() {1969var formatters = $.terminal.defaults.formatters;1970for (let i in formatters) {1971if (formatters[i] === $.terminal.nested_formatting) {1972return i;1973}1974}1975return -1;1976}1977var formatters;1978beforeEach(function() {1979formatters = $.terminal.defaults.formatters.slice();1980});1981afterEach(function() {1982$.terminal.defaults.formatters = formatters;1983});1984it('should add new formatters', function() {1985var formatter_1 = function() {};1986var formatter_2 = [/xxx/, 'xxx'];1987$.terminal.new_formatter(formatter_1);1988var formatters = $.terminal.defaults.formatters;1989var n = nested_index();1990expect(n !== -1).toBeTruthy();1991expect(formatters[n]).toBe($.terminal.nested_formatting);1992expect(formatters[n - 1]).toBe(formatter_1);1993$.terminal.new_formatter(formatter_2);1994n = nested_index();1995expect(formatters[n]).toBe($.terminal.nested_formatting);1996expect(formatters[n - 1]).toBe(formatter_2);1997expect(formatters[n - 2]).toBe(formatter_1);1998});1999it('should add formatter when no nested_formatting', function() {2000var formatter_1 = function() {};2001var formatter_2 = [/xxx/, 'xxx'];2002var formatters = $.terminal.defaults.formatters;2003var n = nested_index();2004expect(n !== -1).toBeTruthy();2005formatters.splice(n, 1);2006expect(nested_index()).toEqual(-1);2007$.terminal.new_formatter(formatter_1);2008$.terminal.new_formatter(formatter_2);2009expect(formatters[formatters.length - 2]).toBe(formatter_1);2010expect(formatters[formatters.length - 1]).toBe(formatter_2);2011});2012});2013describe('$.terminal.less', function() {2014var term;2015var getClientRects;2016var greetings = 'Terminal Less Test';2017var cols = 80, rows = 25;2018var big_text = (function() {2019var lines = [];2020for (var i = 0; i < 100; i++) {2021lines.push('Less ' + i);2022}2023return lines;2024})();2025function get_lines() {2026return term.get_output().split('\n');2027}2028beforeEach(function() {2029term = $('<div/>').terminal($.noop, {2030greetings: greetings,2031numChars: cols,2032numRows: rows2033});2034term.css('width', 800);2035term.focus();2036});2037function key(ord, key) {2038shortcut(false, false, false, ord, key);2039}2040function selected() {2041return term.find('.terminal-output > div div span.terminal-inverted');2042}2043function first(node) {2044var $node = term.find('.terminal-output > div > div:eq(0)');2045if (node) {2046return $node;2047}2048return a0($node.text());2049}2050function search(text) {2051key('/');2052type(text);2053enter_key();2054}2055afterEach(function() {2056term.destroy();2057});2058// mock cursor size - jest/jsdom don't use css2059beforeEach(function() {2060getClientRects = global.window.Element.prototype.getBoundingClientRect;2061global.window.Element.prototype.getBoundingClientRect = function() {2062return {width: 7, height: 14};2063};2064});2065afterEach(function() {2066global.window.Element.prototype.getBoundingClientRect = getClientRects;2067jest.resetAllMocks();2068});2069it('should render big text array', function() {2070term.less(big_text);2071expect(get_lines()).toEqual(big_text.slice(0, rows - 1));2072});2073it('should render big text string', async function() {2074term.less(big_text.join('\n'));2075await delay(10);2076expect(get_lines()).toEqual(big_text.slice(0, rows - 1));2077});2078it('should find 80 line', function() {2079term.less(big_text);2080search('Less 80');2081var sel = selected();2082expect(sel.length).toEqual(1);2083expect(sel.closest('[data-index]').data('index')).toEqual(0);2084});2085it('should ingore case in search', function() {2086term.less(big_text);2087search('less 80');2088var sel = selected();2089expect(sel.length).toEqual(1);2090expect(sel.closest('[data-index]').data('index')).toEqual(0);2091});2092it('should find every line', function() {2093term.less(big_text);2094search('less');2095var sel = selected();2096expect(sel.length).toEqual(rows - 1);2097});2098it('should find inside formatting', function() {2099term.less(big_text.concat(['[[;red;]foo bar baz]']));2100search('bar');2101var spans = term.find('[data-index="0"] > div:first-child span');2102['foo ', 'bar', ' baz'].forEach(function(string, i) {2103expect(a0(spans.eq(i).text())).toEqual(string);2104});2105[true, false, true].forEach(function(check, i) {2106expect([i, spans.eq(i).css('color') === 'red']).toEqual([i, check]);2107});2108expect(spans.eq(1).is('.terminal-inverted')).toBeTruthy();2109});2110it('should navigate inside search results', function() {2111term.less(big_text);2112expect(first()).toEqual('Less 0');2113search('less 1');2114expect(first()).toEqual('Less 1');2115expect(selected().length).toEqual(11); // 1 and 1<num>2116key('n');2117expect(selected().length).toEqual(10);2118for (var i = 0; i < 8; ++i) {2119key('n');2120}2121expect(selected().length).toEqual(2);2122for (i = 0; i < 5; ++i) {2123key('n');2124expect(selected().length).toEqual(1);2125}2126key('p');2127expect(selected().length).toEqual(11);2128expect(first()).toEqual('Less 0');2129key('n');2130expect(first()).toEqual('Less 1');2131key('n');2132expect(selected().length).toEqual(10);2133});2134it('should scroll by line', function() {2135term.less(big_text);2136key('ARROWDOWN');2137key('ARROWDOWN');2138expect(first()).toEqual('Less 2');2139key('ARROWUP');2140expect(first()).toEqual('Less 1');2141});2142it('should exit search', function() {2143term.less(big_text);2144key('ARROWDOWN');2145key('ARROWDOWN');2146key('/');2147type('xxx');2148key(8, 'BACKSPACE');2149expect(term.get_command()).toEqual('xx');2150key(8, 'BACKSPACE');2151expect(term.get_command()).toEqual('x');2152key('ARROWUP');2153key('ARROWUP');2154expect(first()).toEqual('Less 2');2155key(8, 'BACKSPACE');2156expect(term.get_command()).toEqual('');2157expect(term.get_prompt()).toEqual('/');2158key(8, 'BACKSPACE');2159expect(term.get_prompt()).toEqual(':');2160key('ARROWUP');2161key('ARROWUP');2162expect(first()).toEqual('Less 0');2163});2164it('should scroll by page', function() {2165term.less(big_text);2166key('PAGEDOWN');2167key('PAGEDOWN');2168expect(first()).toEqual('Less ' + (rows - 1) * 2);2169key('PAGEUP');2170expect(first()).toEqual('Less ' + (rows - 1));2171key('PAGEUP');2172expect(first()).toEqual('Less 0');2173});2174it('should restore the view', function() {2175term.echo('foo bar');2176term.echo('lorem ipsum');2177var output = term.get_output();2178term.less(big_text);2179key('q');2180expect(term.get_output()).toEqual(output);2181});2182it('should split image', async function() {2183term.settings().numRows = 50;2184term.less('xxx\n[[@;;;;__tests__/Ken_Thompson__and_Dennis_Ritchie_at_PDP-11.jpg]]\nxxx');2185await delay(1000);2186expect(term.get_output().match(/@/g).length).toEqual(43);2187});2188it('should revoke images', async function() {2189term.settings().numRows = 50;2190term.less('xxx\n[[@;;;;__tests__/Ken_Thompson__and_Dennis_Ritchie_at_PDP-11.jpg]]\nxxx');2191await delay(1000);2192spy(URL, 'revokeObjectURL');2193key('q');2194await delay(100);2195expect(URL.revokeObjectURL).toHaveBeenCalledTimes(43);2196});2197it('should render broken image', async function() {2198term.less('xxx\n[[@;;;;error.jpg]]\nxxx');2199await delay(100);2200var err = term.find('.terminal-broken-image');2201expect(err.length).toEqual(1);2202expect(err.text()).toEqual(nbsp('[BROKEN IMAGE]'));2203});2204});2205describe('$.terminal.pipe', function() {2206function get_lines(term, fn = last_divs) {2207return fn(term).map(function() {2208return a0($(this).text());2209}).get();2210}2211function last_divs(term) {2212return term.find('.terminal-output .terminal-command').last().nextUntil();2213}2214function out(term) {2215return get_lines(term, term => term.find('.terminal-output > div'));2216}2217it('should pipe sync command', function() {2218var term = $('<div/>').terminal($.terminal.pipe({2219output: function() {2220this.echo('foo');2221this.echo('bar');2222this.echo('baz');2223},2224grep: function(re) {2225return this.read('').then((str) => {2226var lines = str.split('\n');2227lines.forEach((str) => {2228if (str.match(re)) {2229this.echo(str);2230}2231});2232});2233},2234input: function() {2235return this.read('').then((str) => {2236this.echo('sync: ' + str);2237});2238}2239}));2240return term.exec('output | grep /foo/').then(function() {2241expect(get_lines(term)).toEqual(['foo']);2242}).catch(e => console.log(e));2243});2244it('should escape pipe', async function() {2245var term = $('<div/>').terminal($.terminal.pipe({2246read: function() {2247return this.read('').then((text) => {2248this.echo('your text: ' + text);2249});2250},2251echo: async function(string) {2252await delay(10);2253return string;2254}2255}));2256await term.exec('echo "|" | read');2257expect(get_lines(term)).toEqual(['your text: |']);2258});2259it('should filter lines', function() {2260var term = $('<div/>').terminal($.terminal.pipe({2261output: function(arg) {2262this.echo(arg);2263},2264grep: function(re) {2265return this.read('').then((str) => {2266var lines = str.split('\n');2267lines.forEach((str) => {2268if (str.match(re)) {2269this.echo(str);2270}2271});2272});2273},2274input: function() {2275return this.read('').then((str) => {2276str.split('\n').forEach((str, i) => {2277this.echo(i + ':' + str);2278});2279});2280}2281}));2282return term.exec('output "foo\\nquux\\nbaz\\nfoo\\nbar" | grep /foo|bar/').then(() => {2283expect(get_lines(term)).toEqual(['foo', 'foo', 'bar']);2284return term.exec('output "foo\\nquux\\nbaz\\nfoo\\nbar" | grep /foo|bar/ | input').then(() => {2285expect(get_lines(term)).toEqual([2286'0:foo',2287'1:foo',2288'2:bar'2289]);2290});2291});2292});2293it('should work with async commands', function() {2294var term = $('<div/>').terminal($.terminal.pipe({2295output: function(arg) {2296return new Promise((resolve) => {2297setTimeout(() => {2298this.echo(arg);2299setTimeout(() => resolve(arg), 200);2300}, 200);2301});2302},2303grep: function(re) {2304return this.read('').then((str) => {2305return new Promise((resolve) => {2306setTimeout(() => {2307var lines = str.split('\n');2308lines.forEach((str) => {2309if (str.match(re)) {2310this.echo(str);2311}2312});2313resolve();2314}, 200);2315});2316});2317},2318input: function() {2319return this.read('').then((str) => {2320str.split('\n').forEach((str, i) => {2321this.echo(i + ':' + str);2322});2323});2324}2325}));2326return term.exec('output "foo\\nbar" | grep foo | input').then(() => {2327expect(get_lines(term)).toEqual(['0:foo', '1:foo']);2328});2329});2330it('should work with async read write in first command', async function() {2331var term = $('<div/>').terminal($.terminal.pipe({2332wc: async function(...args) {2333var opts = $.terminal.parse_options(args);2334var text = await this.read('');2335if (text && opts.l) {2336this.echo(text.split('\n').length);2337}2338},2339cat: function() {2340return this.read('').then(text => {2341this.echo(text);2342});2343}2344}));2345term.clear().exec('cat | wc -l');2346await delay(100);2347await term.exec('foo\nbar');2348await delay(100);2349expect(term.get_output().split('\n')).toEqual([2350'> cat | wc -l',2351'foo',2352'bar',2353'2'2354]);2355});2356it('should swallow and prompt for input', async function() {2357function de(...args) {2358return new Promise((resolve) => {2359$.when.apply($, args).then(resolve);2360});2361}2362var fn = jest.fn();2363var prompt = '>>> ';2364var term = $('<div/>').terminal($.terminal.pipe({2365command_1: fn,2366command_2: function(arg) {2367return this.read('input: ').then(fn);2368}2369}), {2370prompt,2371greetings: false2372});2373await term.exec('command_1 | command_2');2374expect(term.get_prompt()).toEqual(prompt);2375expect(fn.mock.calls.length).toEqual(2);2376expect(fn.mock.calls[1][0]).toEqual(undefined);2377term.exec('command_2 | command_1 x');2378await delay(100);2379expect(term.get_prompt()).toEqual('input: ');2380await de(term.exec('foo'));2381await delay(100);2382expect(fn.mock.calls.length).toEqual(4);2383expect(fn.mock.calls[2][0]).toEqual('foo');2384expect(fn.mock.calls[3][0]).toEqual('x');2385});2386it('should create nested interpeter', function() {2387var foo = jest.fn();2388var term = $('<div/>').terminal($.terminal.pipe({2389push: function() {2390this.push({2391foo2392}, {2393prompt: 'push> '2394});2395},2396'new': {2397foo2398}2399}), {2400checkArity: false2401});2402return term.exec(['push', 'foo "hello"']).then(() => {2403expect(foo.mock.calls.length).toEqual(1);2404expect(term.get_prompt()).toEqual('push> ');2405expect(foo.mock.calls[0][0]).toEqual('hello');2406return term.pop().exec(['new', 'foo "hello"']).then(() => {2407expect(foo.mock.calls.length).toEqual(2);2408expect(term.get_prompt()).toEqual('new> ');2409expect(foo.mock.calls[1][0]).toEqual('hello');2410});2411});2412});2413it('should show errors', function() {2414var foo = jest.fn();2415var term = $('<div/>').terminal($.terminal.pipe({2416push: function() {2417this.push({2418foo2419}, {2420prompt: 'push> '2421});2422},2423'new': {2424foo2425}2426}), {2427checkArity: false2428});2429var strings = $.terminal.defaults.strings;2430return term.exec('push | new').then(() => {2431expect(get_lines(term)).toEqual([strings.pipeNestedInterpreterError]);2432expect(last_divs(term).last().find('.terminal-error').length).toEqual(1);2433return term.exec('hello | baz').then(() => {2434expect(get_lines(term)).toEqual([2435sprintf(strings.commandNotFound, 'hello'),2436sprintf(strings.commandNotFound, 'baz')2437]);2438expect(last_divs(term).last().find('.terminal-error').length).toEqual(1);2439return term.exec('quux').then(() => {2440expect(get_lines(term)).toEqual([sprintf(strings.commandNotFound, 'quux')]);2441expect(last_divs(term).last().find('.terminal-error').length).toEqual(1);2442var fn = jest.fn();2443term.settings().onCommandNotFound = fn;2444return term.exec('quux').then(() => {2445expect(get_lines(term)).toEqual([]);2446expect(fn.mock.calls.length).toEqual(1);2447expect(fn.mock.calls[0][0]).toEqual('quux');2448return term.exec('hello | quux').then(() => {2449expect(fn.mock.calls.length).toEqual(2);2450expect(fn.mock.calls[1][0]).toEqual('hello | quux');2451});2452});2453});2454});2455});2456});2457it('should split command', function() {2458var fn = jest.fn();2459var term = $('<div/>').terminal($.terminal.pipe({2460foo: fn2461}), {2462processArguments: false2463});2464return term.exec('foo 10 20 /xx/').then(() => {2465['10', '20', '/xx/'].forEach((arg, i) => {2466expect(fn.mock.calls[0][i]).toEqual(arg);2467});2468});2469});2470describe('redirects', function() {2471var commands = {2472async_output: function(x) {2473return new Promise((resolve) => {2474setTimeout(() => resolve(x), 100);2475});2476},2477output: function(x) {2478this.echo(x);2479},2480grep: function(re) {2481return this.read('').then((str) => {2482var lines = str.split('\n');2483lines.forEach((str) => {2484if (str.match(re)) {2485this.echo(str);2486}2487});2488});2489},2490input: function() {2491return this.read('').then((str) => {2492str.split('\n').forEach((str, i) => {2493this.echo(i + ':' + str);2494});2495});2496}2497};2498it('should redirect simple sync input', function() {2499var term = $('<div/>').terminal($.terminal.pipe(commands, {2500redirects: [2501{2502name: '<<<',2503callback: function(...args) {2504args.forEach(this.echo);2505}2506}2507]2508}));2509return term.exec('input <<< "hello" world').then(() => {2510expect(get_lines(term)).toEqual(['0:hello', '1:world']);2511});2512});2513it('should redirect async input', function() {2514var term = $('<div/>').terminal($.terminal.pipe(commands, {2515redirects: [2516{2517name: '<echo',2518callback: function(...args) {2519return new Promise((resolve) => {2520setTimeout(() => {2521args.forEach(this.echo);2522resolve();2523}, 100);2524});2525}2526},2527{2528name: '<promise',2529callback: function(...args) {2530return new Promise((resolve) => {2531setTimeout(() => resolve(args.join('\n')), 100);2532});2533}2534}2535]2536}));2537return term.exec('input <echo "hello" world').then(() => {2538expect(get_lines(term)).toEqual(['0:hello', '1:world']);2539return term.exec('input <promise "hello" world').then(() => {2540expect(get_lines(term)).toEqual(['0:hello', '1:world']);2541});2542});2543});2544it('should pipe with redirect', function() {2545var term = $('<div/>').terminal($.terminal.pipe(commands, {2546redirects: [2547{2548name: '<echo',2549callback: function(...args) {2550return new Promise((resolve) => {2551setTimeout(() => {2552args.forEach(this.echo);2553resolve();2554}, 100);2555});2556}2557},2558{2559name: '<promise',2560callback: function(...args) {2561return new Promise((resolve) => {2562setTimeout(() => resolve(args.join('\n')), 100);2563});2564}2565}2566]2567}));2568return term.exec('input <echo "hello" world 10 | grep /^[0-9]:h/').then(() => {2569expect(get_lines(term)).toEqual(['0:hello']);2570return term.exec('input <promise "hello" world | grep /^[0-9]:h/').then(() => {2571expect(get_lines(term)).toEqual(['0:hello']);2572return term.exec('input <promise "hello" world | grep /^h/ <echo "hi"').then(() => {2573expect(get_lines(term)).toEqual(['hi']);2574});2575});2576});2577});2578});2579});2580});2581describe('extensions', function() {2582describe('echo_newline', function() {2583var term = $('<div/>').terminal();2584beforeEach(function() {2585term.clear();2586});2587it('should display single line', async function() {2588var prompt = '>>> ';2589term.set_prompt(prompt);2590term.echo('.', {newline: false});2591await delay(10);2592term.echo('.', {newline: false});2593await delay(10);2594term.echo('.', {newline: false});2595await delay(10);2596term.echo('.');2597expect(term.get_output()).toEqual('....');2598expect(term.get_prompt()).toEqual(prompt);2599});2600it('should echo prompt and command', function() {2601var prompt = '>>> ';2602var command = 'hello';2603term.set_prompt(prompt);2604term.echo('.', {newline: false});2605term.echo('.', {newline: false});2606term.echo('.', {newline: false});2607term.exec('hello');2608expect(term.get_output()).toEqual('...' + prompt + command);2609expect(term.get_prompt()).toEqual(prompt);2610});2611it('should echo prompt on enter', function() {2612var prompt = '>>> ';2613var command = 'hello';2614term.set_prompt(prompt);2615term.echo('.', {newline: false});2616term.echo('.', {newline: false});2617term.echo('.', {newline: false});2618enter(term, command);2619expect(term.get_output()).toEqual('...' + prompt + command);2620expect(term.get_prompt()).toEqual(prompt);2621});2622});2623describe('autocomplete_menu', function() {2624function completion(term) {2625return find_menu(term).find('li').map(function() {2626return a0($(this).text());2627}).get();2628}2629function find_menu(term) {2630return term.find('.cmd-cursor-line .cursor-wrapper .cmd-cursor + ul');2631}2632function menu_visible(term) {2633var menu = find_menu(term);2634expect(menu.length).toEqual(1);2635expect(menu.is(':visible')).toBeTruthy();2636}2637function complete(term, text) {2638term.focus().insert(text);2639shortcut(false, false, false, 9, 'tab');2640return delay(50);2641}2642it('should display menu from function with Promise', async function() {2643var term = $('<div/>').terminal($.noop, {2644autocompleteMenu: true,2645completion: function(string) {2646if (!string.match(/_/) && string.length > 3) {2647return Promise.resolve([string + '_foo', string + '_bar']);2648}2649}2650});2651await complete(term, 'hello');2652menu_visible(term);2653expect(term.get_command()).toEqual('hello_');2654expect(completion(term)).toEqual(['foo', 'bar']);2655term.destroy();2656});2657it('should display menu from array', async function() {2658var term = $('<div/>').terminal($.noop, {2659autocompleteMenu: true,2660completion: ['hello_foo', 'hello_bar']2661});2662await complete(term, 'hello');2663menu_visible(term);2664expect(term.get_command()).toEqual('hello_');2665expect(completion(term)).toEqual(['foo', 'bar']);2666term.destroy();2667});2668it('should display menu from Promise<array>', async function() {2669var term = $('<div/>').terminal($.noop, {2670autocompleteMenu: true,2671completion: async function() {2672await delay(10);2673return ['hello_foo', 'hello_bar'];2674}2675});2676complete(term, 'hello');2677await delay(100);2678menu_visible(term);2679expect(term.get_command()).toEqual('hello_');2680expect(completion(term)).toEqual(['foo', 'bar']);2681term.destroy();2682});2683it('should display menu with one element', async function() {2684var term = $('<div/>').terminal($.noop, {2685autocompleteMenu: true,2686completion: ['hello_foo', 'hello_bar']2687});2688term.focus();2689await complete(term, 'hello');2690enter_text('f');2691await delay(50);2692menu_visible(term);2693expect(term.get_command()).toEqual('hello_f');2694expect(completion(term)).toEqual(['oo']);2695shortcut(false, false, false, 9, 'tab');2696await delay(50);2697expect(completion(term)).toEqual([]);2698expect(term.get_command()).toEqual('hello_foo');2699term.destroy();2700});2701});2702});2703describe('sub plugins', function() {2704describe('text_length', function() {2705it('should return length of the text in div', function() {2706var elements = $('<div><span>hello</span><span>world</span>');2707expect(elements.find('span').text_length()).toEqual(10);2708});2709});2710describe('resizer', function() {2711describe('ResizeObserver', function() {2712var div, test, node, callback;2713beforeEach(function() {2714div = $('<div/>');2715test = {2716a: function() {},2717b: function() {}2718};2719spy(test, 'a');2720spy(test, 'b');2721callback = null;2722window.ResizeObserver = function(observer) {2723return {2724unobserve: function() {2725callback = null;2726},2727observe: function(node) {2728callback = observer;2729}2730};2731};2732spy(window, 'ResizeObserver');2733});2734it('should create ResizeObserver', function() {2735div.resizer(function() {});2736expect(div.find('iframe').length).toBe(0);2737expect(window.ResizeObserver).toHaveBeenCalled();2738expect(typeof callback).toBe('function');2739});2740it('should call callback', function() {2741div.resizer(test.a);2742div.resizer(test.b);2743// original ResizeObserver is called on init and plugin skip it2744callback();2745callback();2746expect(test.a).toHaveBeenCalled();2747expect(test.b).toHaveBeenCalled();2748});2749it('should remove resizer', function() {2750div.resizer(test.a);2751div.resizer('unbind');2752expect(callback).toBe(null);2753});2754});2755describe('iframe', function() {2756var div, test;2757beforeEach(function() {2758div = $('<div/>').appendTo('body');2759test = {2760a: function() {},2761b: function() {}2762};2763spy(test, 'a');2764spy(test, 'b');2765delete window.ResizeObserver;2766});2767it('should create iframe', function() {2768div.resizer($.noop);2769expect(div.find('iframe').length).toBe(1);2770});2771it('should trigger callback', function(done) {2772div.resizer(test.a);2773$(div.find('iframe')[0].contentWindow).trigger('resize');2774setTimeout(function() {2775expect(test.a).toHaveBeenCalled();2776done();2777}, 100);2778});2779});2780});2781// stuff not tested in other places2782describe('cmd', function() {2783describe('formatting', function() {2784var formatters = $.terminal.defaults.formatters;2785var cmd;2786beforeEach(function() {2787cmd = $('<div/>').cmd();2788$.terminal.defaults.formatters = formatters.slice();2789$.terminal.defaults.formatters.push([/((?:[a-z\\\]]|])+)/g, '[[;red;]$1]']);2790$.terminal.defaults.formatters.push([/([0-9]]+)/g, '[[;blue;]$1]']);2791cmd.set('');2792});2793afterEach(function() {2794$.terminal.defaults.formatters = formatters;2795cmd.destroy();2796});2797it('should have proper formatting', function() {2798var tests = [2799['foo\\nbar'],2800[2801'foo\\]bar',2802'foo]bar'2803],2804['1111foo\\nbar1111'],2805[2806'1111foo111foo\\nbarr111baz\\]quux111',2807'1111foo111foo\\nbarr111baz]quux111'2808]2809];2810tests.forEach(function(spec) {2811cmd.set(spec[0]);2812var output = spec[1] || spec[0];2813expect(cmd.find('.cmd-wrapper div [data-text]').text()).toEqual(nbsp(output));2814cmd.set('');2815});2816});2817});2818describe('display_position', function() {2819var formatters = $.terminal.defaults.formatters, cmd;2820var text = 'hello foo';2821var rep = 'foo bar';2822var replacement = 'hello ' + rep;2823var len = rep.length;2824function get_pos() {2825return [cmd.position(), cmd.display_position()];2826}2827beforeEach(function() {2828$.terminal.defaults.formatters = formatters.slice();2829$.terminal.defaults.formatters.push([/foo/g, '[[;red;]foo bar]']);2830if (cmd) {2831cmd.destroy();2832}2833cmd = $('<div/>').cmd();2834});2835afterEach(function() {2836$.terminal.defaults.formatters = formatters;2837});2838it('should return corrected position', function() {2839cmd.insert(text);2840expect(cmd.position()).toEqual(text.length);2841expect(cmd.display_position()).toEqual(replacement.length);2842});2843it('should not change position', function() {2844cmd.insert(text);2845var pos = get_pos();2846for (var i = 2; i < len; i++) {2847cmd.display_position(-i, true);2848expect([i, get_pos()]).toEqual([i, pos]);2849}2850});2851it('should change position', function() {2852cmd.insert(text);2853var pos = get_pos();2854expect(get_pos()).toEqual([9, 13]);2855cmd.display_position(-len, true);2856expect(get_pos()).toEqual([6, 6]);2857cmd.display_position(5);2858expect(get_pos()).toEqual([5, 5]);2859cmd.display_position(100);2860expect(get_pos()).toEqual(pos);2861});2862it('should have correct position on text after emoji', async function() {2863// bug #5512864$.terminal.defaults.formatters.push([/:emoji:/g, '[[;red;]*]']);2865cmd.enable();2866var specs = [2867[':emoji:1', ['*1', 7, 1]],2868['aaa:emoji:1', ['aaa*1', 10, 4]],2869['aaa:emoji:1aaaa', ['aaa*1aaaa', 10, 4]],2870[':emoji::emoji:1', ['**1', 14, 2]],2871['aaa:emoji:aaa:emoji:1', ['aaa*aaa*1', 20, 8]],2872['aaa:emoji:1aaa:emoji:aaa', ['aaa*1aaa*aaa', 10, 4]]2873];2874for (var i in specs) {2875var spec = specs[i];2876cmd.set(spec[0]);2877var output = cmd.find('.cmd-wrapper div [data-text]').text();2878click(cmd.find('[data-text="1"]'));2879await delay(300);2880var expected = get_pos();2881expected.unshift(output);2882expect(expected).toEqual(spec[1]);2883}2884});2885});2886});2887});2888describe('Terminal plugin', function() {2889describe('jQuery Terminal options', function() {2890describe('prompt', function() {2891it('should set prompt', function() {2892var prompt = '>>> ';2893var term = $('<div/>').terminal($.noop, {2894prompt: prompt2895});2896expect(term.get_prompt()).toEqual(prompt);2897});2898it('should have default prompt', function() {2899var term = $('<div/>').terminal($.noop);2900expect(term.get_prompt()).toEqual('> ');2901});2902});2903describe('history', function() {2904it('should save data in history', function() {2905var term = $('<div/>').terminal($.noop, {2906history: true,2907name: 'history_enabled'2908});2909expect(term.history().data()).toEqual([]);2910var commands = ['foo', 'bar', 'baz'];2911commands.forEach(function(command) {2912enter(term, command);2913});2914expect(term.history().data()).toEqual(commands);2915});2916it('should not store history', function() {2917var term = $('<div/>').terminal($.noop, {2918history: false,2919name: 'history_disabled'2920});2921expect(term.history().data()).toEqual([]);2922var commands = ['foo', 'bar', 'baz'];2923commands.forEach(function(command) {2924enter(term, command);2925});2926expect(term.history().data()).toEqual([]);2927});2928});2929describe('exit', function() {2930it('should add exit command', function() {2931var term = $('<div/>').terminal($.noop, {2932exit: true2933});2934term.push($.noop);2935expect(term.level()).toEqual(2);2936enter(term, 'exit');2937expect(term.level()).toEqual(1);2938});2939it('should not add exit command', function() {2940var term = $('<div/>').terminal($.noop, {2941exit: false2942});2943term.push($.noop);2944expect(term.level()).toEqual(2);2945enter(term, 'exit');2946expect(term.level()).toEqual(2);2947});2948});2949describe('clear', function() {2950it('should add clear command', function() {2951var term = $('<div/>').terminal($.noop, {2952clear: true2953});2954term.clear().echo('foo').echo('bar');2955expect(term.get_output()).toEqual('foo\nbar');2956enter(term, 'clear');2957expect(term.get_output()).toEqual('');2958});2959it('should not add clear command', function() {2960var term = $('<div/>').terminal($.noop, {2961clear: false2962});2963term.clear().echo('foo').echo('bar');2964expect(term.get_output()).toEqual('foo\nbar');2965enter(term, 'clear');2966expect(term.get_output()).toEqual('foo\nbar\n> clear');2967});2968});2969describe('enabled', function() {2970it('should enable terminal', function() {2971var term = $('<div/>').terminal($.noop, {2972enabled: true2973});2974expect(term.enabled()).toBeTruthy();2975});2976it('should not enable terminal', function() {2977var term = $('<div/>').terminal($.noop, {2978enabled: false2979});2980expect(term.enabled()).toBeFalsy();2981});2982});2983describe('historySize', function() {2984var length = 10;2985var commands = [];2986for (var i = 0; i < 20; ++i) {2987commands.push('command ' + (i+1));2988}2989it('should limit number of history', function() {2990var term = $('<div/>').terminal($.noop, {2991historySize: length,2992greetings: false2993});2994commands.forEach(function(command) {2995enter(term, command);2996});2997var history = term.history().data();2998expect(history.length).toEqual(length);2999expect(history).toEqual(commands.slice(length));3000});3001});3002describe('maskChar', function() {3003function text(term) {3004// without [data-text] is select before cursor span and spans inside3005return term.find('.cmd .cmd-cursor-line span[data-text]:not(.cmd-cursor)').text();3006}3007it('should use specified character for mask', function() {3008var mask = '-';3009var term = $('<div/>').terminal($.noop, {3010maskChar: mask,3011greetings: false3012});3013term.set_mask(true);3014var command = 'foo bar';3015term.insert(command);3016expect(text(term)).toEqual(command.replace(/./g, mask));3017});3018});3019describe('wrap', function() {3020var term = $('<div/>').terminal($.noop, {3021wrap: false,3022greetings: false,3023numChars: 1003024});3025it('should not wrap text', function() {3026var line = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ultrices rhoncus hendrerit. Nunc ligula eros, tincidunt posuere tristique quis, iaculis non elit.';3027term.echo(line);3028var output = last_div(term);3029expect(output.find('span').length).toEqual(1);3030expect(output.find('div').length).toEqual(1);3031});3032it('should not wrap formatting', function() {3033var term = $('<div/>').terminal($.noop, {3034wrap: false,3035greetings: false,3036numChars: 1003037});3038var line = '[[;#fff;]Lorem ipsum dolor sit amet], consectetur adipiscing elit. [[;#fee;]Cras ultrices rhoncus hendrerit.] Nunc ligula eros, tincidunt posuere tristique quis, [[;#fff;]iaculis non elit.]';3039term.echo(line);3040var output = last_div(term);3041expect(output.find('span').length).toEqual(5); // 3 formattings and 2 between3042expect(output.find('div').length).toEqual(1);3043});3044});3045describe('checkArity', function() {3046var interpreter = {3047foo: function(a, b) {3048a = a || 10;3049b = b || 10;3050this.echo(a + b);3051}3052};3053var term = $('<div/>').terminal(interpreter, {3054greetings: false,3055checkArity: false3056});3057it('should call function with no arguments', function() {3058spy(interpreter, 'foo');3059enter(term, 'foo');3060expect(interpreter.foo).toHaveBeenCalledWith();3061});3062it('should call function with one argument', function() {3063spy(interpreter, 'foo');3064enter(term, 'foo 10');3065expect(interpreter.foo).toHaveBeenCalledWith(10);3066});3067});3068describe('raw', function() {3069var term = $('<div/>').terminal($.noop, {3070raw: true3071});3072beforeEach(function() {3073term.clear();3074});3075var img = '<img src="http://lorempixel.com/300/200/cats/"/>';3076it('should display html when no raw echo option is specified', function() {3077term.echo(img);3078expect(last_div(term).find('img').length).toEqual(1);3079});3080it('should display html as text when using raw echo option', function() {3081term.echo(img, {raw: false});3082var output = last_div(term);3083expect(output.find('img').length).toEqual(0);3084expect(output.text().replace(/\s/g, ' ')).toEqual(img);3085});3086});3087describe('exceptionHandler', function() {3088var test = {3089exceptionHandler: function(e) {3090}3091};3092var exception = new Error('some exception');3093it('should call exception handler with thrown error', function() {3094spy(test, 'exceptionHandler');3095try {3096var term = $('<div/>').terminal(function() {3097throw exception;3098}, {3099greetings: false,3100exceptionHandler: test.exceptionHandler3101});3102} catch(e) {}3103enter(term, 'foo');3104expect(term.find('.terminal-error').length).toEqual(0);3105expect(test.exceptionHandler).toHaveBeenCalledWith(exception, 'USER');3106});3107});3108describe('pauseEvents', function() {3109var options = {3110pauseEvents: false,3111keypress: function(e) {3112},3113keydown: function(e) {3114}3115};3116var term;3117beforeEach(function() {3118spy(options, 'keypress');3119spy(options, 'keydown');3120});3121it('should execute keypress and keydown when terminal is paused', function() {3122term = $('<div/>').terminal($.noop, options);3123term.pause();3124shortcut(false, false, false, 32, ' ');3125expect(options.keypress).toHaveBeenCalled();3126expect(options.keydown).toHaveBeenCalled();3127});3128it('should not execute keypress and keydown', function() {3129options.pauseEvents = true;3130term = $('<div/>').terminal($.noop, options);3131term.pause();3132shortcut(false, false, false, 32, ' ');3133expect(options.keypress).not.toHaveBeenCalled();3134expect(options.keydown).not.toHaveBeenCalled();3135});3136});3137});3138describe('terminal create / terminal destroy', function() {3139var term = $('<div/>').appendTo('body').terminal();3140it('should create terminal', function() {3141expect(term.length).toBe(1);3142});3143it('should have proper elements', function() {3144expect(term.hasClass('terminal')).toBe(true);3145expect(term.find('.terminal-output').length).toBe(1);3146expect(term.find('.cmd').length).toBe(1);3147var prompt = term.find('.cmd-prompt');3148expect(prompt.length).toBe(1);3149expect(prompt.is('span')).toBe(true);3150expect(prompt.children().length).toBe(1);3151var cursor = term.find('.cmd-cursor');3152expect(cursor.length).toBe(1);3153expect(cursor.is('span')).toBe(true);3154expect(cursor.prev().is('span')).toBe(true);3155expect(cursor.next().is('span')).toBe(true);3156term.focus().cmd().enable();3157//this check sometimes fail in travis3158//expect(cursor.hasClass('blink')).toBe(true);3159expect(term.find('.cmd-clipboard').length).toBe(1);3160});3161it('should have signature', function() {3162var sig = term.find('.terminal-output div div').map(function() { return $(this).text(); }).get().join('\n');3163expect(nbsp(term.signature())).toEqual(sig);3164});3165it('should have default prompt', function() {3166var prompt = term.find('.cmd-prompt');3167expect(prompt.html()).toEqual("<span data-text=\"> \">> </span>");3168expect(prompt.text()).toEqual(nbsp('> '));3169});3170it('should destroy terminal', function() {3171term.destroy();3172expect(term.children().length).toBe(0);3173term.remove();3174});3175it('should create multiple terminals', function() {3176var divs = $('<div><div/><div/></div>');3177divs.find('div').terminal();3178expect(divs.find('.terminal').length).toEqual(2);3179});3180it('should return previously created terminal', function() {3181var div = $('<div/>');3182div.terminal();3183var test = {3184fn: $.noop3185};3186spy(test, 'fn');3187var term = div.terminal($.noop, {3188onInit: test.fn3189});3190expect(test.fn).not.toHaveBeenCalled();3191expect(typeof term.login).toEqual('function');3192});3193it('should throw exception on empty selector', function() {3194var strings = $.terminal.defaults.strings;3195var error = new $.terminal.Exception(strings.invalidSelector);3196expect(function() {3197$('#notExisting').terminal();3198}).toThrow(error);3199});3200});3201describe('cursor', function() {3202it('only one terminal should have blinking cursor', function() {3203var term1 = $('<div/>').appendTo('body').terminal($.noop);3204term1.focus();3205var term2 = $('<div/>').appendTo('body').terminal($.noop);3206term1.pause();3207term2.focus();3208return delay(100, function() {3209term1.resume();3210expect($('.cmd-cursor.cmd-blink').length).toEqual(1);3211term1.destroy().remove();3212term2.destroy().remove();3213});3214});3215});3216describe('observers', function() {3217var i_callback, m_callback, test, term, s_callback;3218function init() {3219beforeEach(function() {3220test = {3221fn: $.noop3222};3223spy(test, 'fn');3224i_callback = [];3225s_callback = [];3226window.IntersectionObserver = function(callback, options) {3227return {3228observe: function(node) {3229if (options.root === null) {3230i_callback.push(callback);3231} else {3232s_callback.push(callback);3233}3234},3235unobserve: function() {3236for (var i = i_callback.length; --i;) {3237if (i_callback[i] === callback) {3238i_callback.slice(i, 1);3239break;3240}3241}3242}3243};3244};3245window.MutationObserver = function(callback) {3246m_callback = callback;3247return {3248observe: function() {3249},3250disconnect: function() {3251m_callback = null;3252}3253};3254};3255global.IntersectionObserver = window.IntersectionObserver;3256term = $('<div/>').appendTo('body').terminal($.noop, {3257onResize: test.fn3258});3259});3260afterEach(function() {3261term.destroy().remove();3262// we only need observers in this tests3263delete global.IntersectionObserver;3264delete window.IntersectionObserver;3265delete window.MutationObserver;3266});3267}3268describe('MutationObserver', function() {3269init();3270it('should call resize', function() {3271term.detach();3272m_callback();3273term.appendTo('body');3274m_callback();3275expect(test.fn).toHaveBeenCalled();3276});3277});3278describe('IntersectionObserver', function() {3279init();3280it('should enable/disable terminal', function() {3281expect(term.enabled()).toBe(true);3282i_callback[0]();3283term.hide();3284i_callback[0]();3285expect(term.enabled()).toBe(false);3286term.show();3287i_callback[0]();3288expect(term.enabled()).toBe(true);3289});3290it('should call resize', function() {3291i_callback[0]();3292term.hide();3293i_callback[0]();3294term.show();3295i_callback[0]();3296expect(test.fn).toHaveBeenCalled();3297});3298});3299});3300function without_formatters(fn) {3301return function() {3302var formatters = $.terminal.defaults.formatters;3303$.terminal.defaults.formatters = [];3304var ret = fn();3305$.terminal.defaults.formatters = formatters;3306return ret;3307};3308}3309describe('events', function() {3310describe('click', function() {3311var term = $('<div/>').terminal($.noop, {greetings: false, clickTimeout: 0});3312var cmd = term.cmd();3313beforeEach(function() {3314term.focus().set_command('');3315});3316it('should move cursor to click position', function() {3317var text = 'foo\nbar\nbaz';3318term.insert(text).focus();3319for (var pos = 0; pos < text.length; ++pos) {3320var node = cmd.find('.cmd-wrapper div span[data-text]').eq(pos);3321click(node);3322expect(term.get_position()).toBe(pos);3323}3324});3325it('should ignore formatting inside cmd', without_formatters(function() {3326var text = '[[;;]hello] [[bui;;]world]';3327term.insert(text).focus();3328for (var pos = 0; pos < text.length; ++pos) {3329var node = cmd.find('.cmd-wrapper div span[data-text]').eq(pos);3330click(node);3331expect(term.get_position()).toBe(pos);3332expect(term.cmd().display_position()).toBe(pos);3333}3334}));3335it('should move cursor when text have emoji', function() {3336var text = '\u263a\ufe0f xxxx \u261d\ufe0f xxxx \u0038\ufe0f\u20e3';3337var chars = $.terminal.split_characters(text);3338term.insert(text).focus();3339expect(term.find('.cmd .cmd-wrapper div span[data-text]').length).toBe(15);3340// indexes of emoji3341[0, 7, 14].forEach(function(pos) {3342var node = cmd.find('.cmd-wrapper div span[data-text]').eq(pos);3343click(node);3344expect(cmd.display_position()).toBe(pos);3345var char = chars[pos];3346expect(cmd.find('.cmd-cursor [data-text] span').text().length)3347.toEqual(char.length);3348expect(char.length).toBeGreaterThan(1);3349});3350});3351function test_click(spec) {3352var input_str = spec[0];3353var output_str = spec[1];3354term.set_command(input_str).focus();3355for (var pos = 0, len = $.terminal.length(input_str); pos < len; ++pos) {3356var node = cmd.find('.cmd-wrapper div span[data-text]').eq(pos);3357click(node);3358expect(cmd.display_position()).toBe(pos);3359var output = cmd.find('[role="presentation"]').map(function() {3360return $(this).text().replace(/\xA0/g, ' ');3361}).get().join('\n');3362expect([pos, output]).toEqual([pos, output_str]);3363}3364}3365it('should move cursor when over formatting', without_formatters(function() {3366false && ($.terminal.defaults.formatters = [3367function(string, options) {3368var result = [string, options.position];3369['\u0038\ufe0f\u20e3', '\u263a\ufe0f'].forEach(function(emoji) {3370result = $.terminal.tracking_replace(3371result[0],3372/(\u0038\ufe0f\u20e3|\u263a\ufe0f)/g,3373'[[;;;emoji]$1]',3374result[1]);3375});3376return result;3377}3378]);3379var test = [3380'\u263a\ufe0foo\tbar\t\t\u263a\ufe0fa\u0038\ufe0f\u20e3\nfoo\t\tb\t\tbaz\nfoobar\tba\t\tbr',3381'\u263a\ufe0foo bar \u263a\ufe0fa\u0038\ufe0f\u20e3 \nfoo b baz \nfoobar ba br'3382];3383test_click(test);3384}));3385it('should align tabs', function() {3386var tests = [3387[3388'fo\tbar\tbaz\nf\t\tb\tbaz\nfa\t\tba\tbr',3389'fo bar baz \nf b baz \nfa ba br'3390],3391[3392'\u263a\ufe0foo\tbar\t\t\u263a\ufe0fa\u0038\ufe0f\u20e3\nfoo\t\tb\t\tbaz\nfoobar\tba\t\tbr',3393'\u263a\ufe0foo bar \u263a\ufe0fa\u0038\ufe0f\u20e3 \nfoo b baz \nfoobar ba br'3394]3395];3396tests.forEach(test_click);3397});3398it('should move cursor on text with backspaces', function() {3399var input = [3400'Test 0.\n\n==============================\nState1.\t[ ]\b\b\b\b\b--\r',3401'\u001B[KState1.\t[ ]\b\b\b\b\bDONE\nLine2.\t[ ]\b\b\b\b\b----\b\b',3402'\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b',3403'\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b\b\b \b\b\b\b----\b\b',3404'\b\b \b\b\b\b-\r\u001B[KLin2.\t[ ]\b\b\b\b\bFAIL\nTest3.\t[ ]\b',3405'\b\b\b\b--\r\u001B[KTest3.\t[ ]\b\b\b\b\bWARNING]\n\nFinal status\n\n',3406'Status details\nTime: 11'3407].join('');3408var output = $.terminal.apply_formatters(input).split('\n');3409function get_count(i) {3410return output.slice(0, i + 1).reduce(function(acc, line) {3411return acc + line.length;3412}, 0) + i;3413}3414term.insert(input);3415return new Promise(function(resolve) {3416setTimeout(function() {3417var given = [];3418var expected = [];3419(function loop(i) {3420var lines = term.find('.cmd [role="presentation"]');3421if (i === lines.length) {3422expect(given).toEqual(expected);3423return resolve();3424}3425click(lines.eq(i));3426var count = get_count(i);3427given.push(cmd.display_position());3428expected.push(count);3429loop(i+1);3430})(0);3431}, 100);3432}).then(function() {3433return new Promise(function(resolve) {3434var line = 5;3435(function loop(i) {3436var chars = term.focus().find('.cmd [role="presentation"]')3437.eq(line).find('span[data-text]');3438if (i === chars.length) {3439return resolve();3440}3441click(chars.eq(i).find('span'));3442expect(cmd.display_position()).toEqual(i + 68);3443loop(i+1);3444})(0);3445});3446});3447});3448});3449describe('contextmenu', function() {3450var term = $('<div/>').terminal();3451it('should move textarea', function() {3452function have_props(props) {3453var style = clip.attr('style');3454var style_props = style.split(/\s*;\s*/).filter(Boolean).map(function(pair) {3455pair = pair.split(/\s*:\s*/);3456return pair[0];3457});3458return props.every(function(prop) {3459return style_props.includes(prop);3460});3461}3462var cmd = term.cmd();3463var clip = term.find('textarea');3464var event = new $.Event('contextmenu');3465expect(have_props(['height', 'width'])).toBeFalsy();3466event.pageX = 100;3467event.pageY = 100;3468cmd.trigger(event);3469expect(have_props(['height', 'width'])).toBeTruthy();3470return delay(200, function() {3471expect(have_props(['height', 'width'])).toBeFalsy();3472});3473});3474});3475describe('input', function() {3476var doc = $(document.documentElement || window);3477it('should trigger keypress from input', function(done) {3478// trigger input without keypress3479var term = $('<div/>').terminal();3480term.focus();3481var clip = term.find('textarea');3482clip.val(clip.val() + 'a');3483doc.one('keypress', function(e) {3484expect(e.key).toEqual('a');3485setTimeout(function() {3486expect(term.get_command()).toEqual('a');3487done();3488}, 200);3489});3490doc.trigger(keydown(false, false, false, 'a'));3491doc.trigger('input');3492});3493it('should trigger keydown from input', function(done) {3494// trigger input without keydown3495var term = $('<div/>').terminal();3496term.focus();3497term.insert('foo bar');3498var clip = term.find('textarea');3499clip.val(clip.val().replace(/.$/, ''));3500doc.one('keydown', function(e) {3501expect(e.which).toBe(8);3502setTimeout(function() {3503expect(term.get_command()).toEqual('foo ba');3504// the code before will trigger dead key - in Android it's always3505// no keypress or no keydown for all keys3506doc.trigger(keypress('a', true));3507done();3508}, 200);3509});3510doc.trigger('input');3511});3512});3513describe('enter text', function() {3514var interpreter = {3515foo: function() {3516}3517};3518var term = $('<div/>').appendTo('body').terminal(interpreter);3519it('text should appear and interpreter function should be called', function() {3520term.clear().focus(true);3521spy(interpreter, 'foo');3522enter_text('foo');3523expect(term.get_command()).toEqual('foo');3524enter_key();3525expect(interpreter.foo).toHaveBeenCalled();3526var last_div = term.find('.terminal-output > div:last-child');3527expect(last_div.hasClass('terminal-command')).toBe(true);3528expect(last_div.children().html()).toEqual('<span>> foo</span>');3529term.destroy().remove();3530});3531});3532});3533describe('prompt', function() {3534var term = $('<div/>').appendTo('body').terminal($.noop, {3535prompt: '>>> '3536});3537it('should return prompt', function() {3538expect(term.get_prompt()).toEqual('>>> ');3539expect(term.find('.cmd-prompt').html()).toEqual('<span data-text=">>> ">' +3540'>>> </span>');3541});3542it('should set prompt', function() {3543term.set_prompt('||| ');3544expect(term.get_prompt()).toEqual('||| ');3545expect(term.find('.cmd-prompt').html()).toEqual('<span data-text=\"||| \">||| </span>');3546function prompt(callback) {3547callback('>>> ');3548}3549term.set_prompt(prompt);3550expect(term.get_prompt()).toEqual(prompt);3551expect(term.find('.cmd-prompt').html()).toEqual('<span data-text=">>> ">' +3552'>>> </span>');3553});3554it('should format prompt', function() {3555var prompt = '<span style="font-weight:bold;text-decoration:underline;color:'+3556'#fff;--color:#fff;" data-text=">>>">>>></span><span> '+3557'</span>';3558term.set_prompt('[[ub;#fff;]>>>] ');3559expect(term.find('.cmd-prompt').html()).toEqual(prompt);3560term.set_prompt(function(callback) {3561callback('[[ub;#fff;]>>>] ');3562});3563expect(term.find('.cmd-prompt').html()).toEqual(prompt);3564term.destroy().remove();3565});3566});3567describe('cmd plugin', function() {3568var term = $('<div/>').appendTo('body').css('overflow-y', 'scroll').terminal($.noop, {3569name: 'cmd',3570numChars: 150,3571numRows: 203572});3573var string = '';3574for (var i=term.cols(); i--;) {3575term.insert('M');3576}3577var cmd = term.cmd();3578var line = cmd.find('.cmd-prompt').next();3579it('text should have 2 lines', function() {3580expect(line.is('div')).toBe(true);3581expect(line.text().length).toBe(term.cols()-2);3582});3583it('cmd plugin moving cursor', function() {3584cmd.position(-8, true);3585var cursor_line = cmd.find('.cmd-cursor-line');3586var cursor = cmd.find('.cmd-cursor');3587var before = cursor.prev();3588var after = cursor.next();3589expect(before.is('span')).toBe(true);3590expect(before.text().length).toBe(term.cols()-8);3591expect(cursor_line.next().text().length).toBe(2);3592expect(after.text().length).toBe(5);3593expect(cursor.text()).toBe('M');3594});3595it('should remove characters', function() {3596cmd['delete'](-10);3597var cursor = cmd.find('.cmd-cursor');3598var before = cursor.prev();3599var after = cursor.next();3600expect(before.text().length).toEqual(term.cols()-8-10);3601cmd['delete'](8);3602expect(cursor.text()).toEqual('\xA0');3603expect(after.text().length).toEqual(0);3604});3605var history = cmd.history();3606it('should have one entry in history', function() {3607cmd.purge();3608term.set_command('something').focus(true);3609enter_key();3610expect(history.data()).toEqual(['something']);3611});3612it('should not add item to history if history is disabled', function() {3613history.disable();3614term.set_command('something else');3615enter_key();3616expect(history.data()).toEqual(['something']);3617});3618it('should remove commands from history', function() {3619spy(history, 'purge');3620cmd.purge();3621expect(history.purge).toHaveBeenCalled();3622expect(history.data()).toEqual([]);3623});3624it('should have name', function() {3625expect(cmd.name()).toEqual('cmd_' + term.id());3626});3627it('should return command', function() {3628cmd.set('foo');3629expect(cmd.get()).toEqual('foo');3630});3631it('should not move position', function() {3632var pos = cmd.position();3633cmd.insert('bar', true);3634expect(cmd.position()).toEqual(pos);3635});3636it('should return $.noop for commands', function() {3637expect($.terminal.active().commands()).toEqual($.noop);3638});3639it('should set position', function() {3640cmd.position(0);3641expect(cmd.position()).toEqual(0);3642});3643it('should set and remove mask', function() {3644cmd.mask('•');3645cmd.position(6);3646var before = cmd.find('.cmd-cursor').prev();3647expect(before.text()).toEqual('••••••');3648expect(cmd.get()).toEqual('foobar');3649cmd.mask(false);3650expect(before.text()).toEqual('foobar');3651});3652it('should execute functions on shortcuts', function() {3653spy(cmd, 'position');3654shortcut(true, false, false, 65, 'a'); // CTRL+A3655expect(cmd.position).toHaveBeenCalled();3656spy(cmd, 'delete');3657shortcut(true, false, false, 75, 'k'); // CTRL+K3658expect(cmd['delete']).toHaveBeenCalled();3659spy(cmd, 'insert');3660shortcut(true, false, false, 89, 'y'); // CTRL+Y3661expect(cmd.insert).toHaveBeenCalled();3662shortcut(true, false, false, 85, 'u'); // CTRL+U3663expect(cmd.kill_text()).toEqual('foobar');3664shortcut(false, false, true, 13, 'enter');3665expect(cmd.find('.cmd-prompt').next().text()).toEqual('\xA0');3666expect(cmd.get()).toEqual('\n');3667cmd.set('');3668shortcut(false, false, false, 9, 'tab'); // TAB3669expect(cmd.get()).toEqual('\t');3670history.enable();3671cmd.set('foo bar');3672enter_key();3673shortcut(false, false, false, 38, 'ArrowUp'); // UP ARROW3674expect(cmd.get()).toEqual('foo bar');3675shortcut(false, false, false, 40, 'arrowDown'); // DOWN ARROW3676expect(cmd.get()).toEqual('');3677cmd.insert('hello');3678shortcut(false, false, false, 38, 'arrowUp');3679shortcut(false, false, false, 40, 'arrowDown');3680expect(cmd.get()).toEqual('hello');3681shortcut(false, false, false, 38, 'arrowUp');3682enter_key();3683shortcut(false, false, false, 38, 'arrowUp');3684expect(cmd.get()).toEqual('foo bar');3685enter_key();3686shortcut(false, false, false, 38, 'arrowUp');3687expect(cmd.get()).toEqual('foo bar');3688shortcut(false, false, false, 40, 'arrowDown');3689cmd.insert('hello');3690shortcut(true, false, false, 80, 'p'); // CTRL+P3691expect(cmd.get()).toEqual('foo bar');3692shortcut(true, false, false, 78, 'n'); // CTRL+N3693expect(cmd.get()).toEqual('hello');3694cmd.set('foo bar baz');3695shortcut(false, false, false, 37, 'arrowleft'); // LEFT ARROW3696expect(cmd.position()).toEqual(10);3697shortcut(true, false, false, 37, 'arrowleft'); // moving by words3698expect(cmd.position()).toEqual(8);3699shortcut(true, false, false, 37, 'arrowleft');3700expect(cmd.position()).toEqual(4);3701shortcut(true, false, false, 37, 'arrowleft');3702expect(cmd.position()).toEqual(0);3703shortcut(false, false, false, 39, 'arrowright'); // RIGHT ARROW3704expect(cmd.position()).toEqual(1);3705shortcut(true, false, false, 39, 'arrowright');3706expect(cmd.position()).toEqual(3);3707shortcut(true, false, false, 39, 'arrowright');3708expect(cmd.position()).toEqual(7);3709shortcut(true, false, false, 39, 'arrowright');3710expect(cmd.position()).toEqual(11);3711shortcut(false, false, false, 36, 'home'); // HOME3712expect(cmd.position()).toEqual(0);3713shortcut(false, false, false, 35, 'end'); // END3714expect(cmd.position()).toEqual(cmd.get().length);3715shortcut(true, false, false, 82, 'r'); // CTRL+R3716expect(cmd.prompt()).toEqual("(reverse-i-search)`': ");3717enter_text('foo');3718expect(cmd.get()).toEqual('foo bar');3719shortcut(true, false, false, 71, 'g'); // CTRL+G3720expect(cmd.get()).toEqual('foo bar baz');3721expect(cmd.prompt()).toEqual("> ");3722shortcut(true, false, false, 82, 'r'); // CTRL+R3723expect(cmd.prompt()).toEqual("(reverse-i-search)`': ");3724shortcut(false, false, false, 36, 'Home');3725expect(cmd.prompt()).toEqual("> ");3726});3727it('should move cursor', function() {3728var key = shortcut.bind(null, false, false, false);3729function left() {3730key(37, 'arrowleft');3731}3732function right() {3733key(39, 'arrowright');3734}3735function up() {3736key(38, 'arrowup');3737}3738function down() {3739key(40, 'arrowdown');3740}3741function with_newlines(i) {3742return lines.slice(0, i).map(function(line) {3743return line.length + 1;3744}).reduce(function(a, b) {3745return a + b;3746});3747}3748var lines = [3749'First Line of Text',3750'Second Line of Text',3751'Thrid Line of Text'3752];3753var command = lines.join('\n');3754term.focus();3755cmd.set(command);3756cmd.position(0);3757right();3758right();3759right();3760right();3761right();3762expect(cmd.position()).toEqual(5);3763down();3764expect(cmd.position()).toEqual(with_newlines(1) + 5 + cmd.prompt().length);3765down();3766expect(cmd.position()).toEqual(with_newlines(2) + 5 + cmd.prompt().length);3767left();3768up();3769expect(cmd.position()).toEqual(with_newlines(1) + 4 + cmd.prompt().length);3770up();3771expect(cmd.position()).toEqual(4);3772cmd.purge();3773term.destroy().remove();3774});3775});3776function AJAXMock(url, response, options) {3777var ajax = $.ajax;3778options = $.extend({}, {3779async: false3780}, options);3781$.ajax = function(obj) {3782function done() {3783if (!canceled) {3784if ($.isFunction(obj.success)) {3785obj.success(response, 'OK', {3786getResponseHeader: function(header) {3787if (header == 'Content-Type') {3788return 'application/json';3789}3790},3791responseText: response3792});3793}3794defer.resolve(response);3795}3796}3797if (obj.url == url) {3798var defer = $.Deferred();3799var canceled = false;3800var jqXHR = {3801abort: function() {3802canceled = true;3803}3804};3805$(document).trigger("ajaxSend", [ jqXHR, obj ] );3806try {3807if ($.isFunction(obj.beforeSend)) {3808obj.beforeSend({}, obj);3809}3810if (options.async) {3811var num = +options.async;3812setTimeout(done, isNaN(num) ? 100 : num);3813} else {3814done();3815}3816} catch (e) {3817throw new Error(e.message);3818}3819return defer.promise();3820} else {3821return ajax.apply($, arguments);3822}3823};3824}3825function JSONRPCMock(url, object, options) {3826var defaults = {3827no_system_describe: false,3828async: false,3829error: $.noop3830};3831var settings = $.extend({}, defaults, options);3832var ajax = $.ajax;3833var system = {3834'sdversion': '1.0',3835'name': 'DemoService',3836'address': url,3837// md5('JSONRPCMock')3838'id': 'urn:md5:e1a975ac782ce4ed0a504ceb909abf44',3839'procs': []3840};3841for (var key in object) {3842var proc = {3843name: key3844};3845if ($.isFunction(object[key])) {3846var re = /function[^\(]+\(([^\)]+)\)/;3847var m = object[key].toString().match(re);3848if (m) {3849proc.params = m[1].split(/\s*,\s*/);3850}3851}3852system.procs.push(proc);3853}3854$.ajax = function(obj) {3855function done() {3856if ($.isFunction(obj.success)) {3857obj.success(resp, 'OK', {3858getResponseHeader: function(header) {3859if (header == 'Content-Type') {3860return 'application/json';3861}3862}3863});3864}3865defer.resolve(resp);3866}3867if (obj.url == url) {3868var defer = $.Deferred();3869try {3870obj.beforeSend({}, obj);3871var req = JSON.parse(obj.data);3872var resp;3873if (req.method == 'system.describe') {3874if (!settings.no_system_describe) {3875resp = system;3876} else {3877var data = obj.data;3878if (typeof data == 'string') {3879data = JSON.parse(data);3880}3881resp = {3882"jsonrpc": "2.0",3883"result": null,3884"id": data.id,3885"error": {3886"code": -32601,3887"message": "There is no system.describe method"3888}3889};3890}3891} else {3892var error = null;3893var ret = null;3894try {3895if ($.isFunction(object[req.method])) {3896ret = object[req.method].apply(null, req.params);3897} else {3898ret = null;3899error = {3900"code": -32601,3901"message": "There is no `" + req.method + "' method"3902};3903}3904} catch (e) {3905error = {3906message: e.message,3907error: {3908file: '/foo/bar/baz.php',3909at: 10,3910message: 'Syntax Error'3911}3912};3913}3914resp = {3915id: req.id,3916jsonrpc: '1.1',3917result: ret,3918error: error3919};3920}3921resp = JSON.stringify(resp);3922if (settings.async) {3923setTimeout(done, 5);3924} else {3925done();3926}3927} catch (e) {3928throw new Error(e.message);3929}3930return defer.promise();3931} else {3932return ajax.apply($, arguments);3933}3934};3935}3936var token = 'TOKEN';3937var exception = 'Some Exception';3938var object = {3939echo: function(token, str) {3940return str;3941},3942foo: function(token, obj) {3943return obj;3944},3945exception: function(token) {3946throw new Error(exception);3947},3948login: function(user, password) {3949if (user == 'demo' && password == 'demo') {3950return token;3951} else {3952return null;3953}3954}3955};3956AJAXMock('/not-json', 'Response', {async: true});3957AJAXMock('/not-rpc', '{"foo": "bar"}', {async: true});3958JSONRPCMock('/test', object);3959JSONRPCMock('/no_describe', object, {no_system_describe: true});3960JSONRPCMock('/async', object, {async: true});3961describe('JSON-RPC', function() {3962var term = $('<div/>').appendTo('body').terminal('/test', {3963login: true3964});3965it('should call login', function() {3966if (term.token()) {3967term.logout();3968}3969term.focus();3970spy(object, 'login');3971enter(term, 'test');3972enter(term, 'test');3973var last_div = term.find('.terminal-output > div:last-child');3974expect(last_div.text()).toEqual('Wrong password try again!');3975expect(object.login).toHaveBeenCalledWith('test', 'test');3976enter(term, 'demo');3977enter(term, 'demo');3978expect(object.login).toHaveBeenCalledWith('demo', 'demo');3979expect(term.token()).toEqual(token);3980});3981it('should call a function', function() {3982term.focus();3983spy(object, 'echo');3984enter(term, 'echo hello');3985expect(object.echo).toHaveBeenCalledWith(token, 'hello');3986term.destroy().remove();3987});3988describe('No system.describe', function() {3989it('should call login rpc method', function() {3990term = $('<div/>').appendTo('body').terminal('/no_describe', {3991login: true3992});3993if (term.token()) {3994term.logout();3995}3996spy(object, 'login');3997enter(term, 'demo');3998enter(term, 'demo');3999expect(object.login).toHaveBeenCalledWith('demo', 'demo');4000});4001it('should pass TOKEN to method', function() {4002spy(object, 'echo');4003enter(term, 'echo hello');4004expect(object.echo).toHaveBeenCalledWith(token, 'hello');4005term.destroy().remove();4006});4007it('should call login function', function() {4008var options = {4009login: function(user, password, callback) {4010if (user == 'foo' && password == 'bar') {4011callback(token);4012} else {4013callback(null);4014}4015}4016};4017spy(options, 'login');4018spy(object, 'echo');4019term = $('<div/>').appendTo('body').terminal('/no_describe',4020options);4021if (term.token()) {4022term.logout();4023}4024enter(term, 'test');4025enter(term, 'test');4026expect(options.login).toHaveBeenCalled();4027expect(term.token()).toBeFalsy();4028enter(term, 'foo');4029enter(term, 'bar');4030expect(options.login).toHaveBeenCalled();4031expect(term.token()).toEqual(token);4032enter(term, 'echo hello');4033expect(object.echo).toHaveBeenCalledWith(token, 'hello');4034term.destroy().remove();4035});4036it('should ignore system.describe method', function() {4037term = $('<div/>').appendTo('body').terminal('/test', {4038describe: false,4039completion: true4040});4041expect(term.export_view().interpreters.top().completion).toBeFalsy();4042term.destroy().remove();4043});4044it('should display error on invalid JSON', function(done) {4045var term = $('<div/>').appendTo('body').terminal('/not-json', {greetings: false});4046setTimeout(function() {4047enter(term, 'foo');4048setTimeout(function() {4049var output = [4050'> foo',4051'[[;;;terminal-error][AJAX] Invalid JSON - Server responded:',4052'Response]'4053].join('\n');4054expect(term.get_output()).toEqual(output);4055term.destroy().remove();4056done();4057}, 200);4058}, 200);4059});4060it('should display error on Invalid JSON-RPC response', function(done) {4061var term = $('<div/>').appendTo('body').terminal('/not-rpc', {4062greetings: false4063});4064setTimeout(function() {4065enter(term, 'foo');4066setTimeout(function() {4067var output = [4068'> foo',4069'[[;;;terminal-error][AJAX] Invalid JSON-RPC - Server responded:',4070'{"foo": "bar"}]'4071].join('\n');4072expect(term.get_output()).toEqual(output);4073term.destroy().remove();4074done();4075}, 200);4076}, 200);4077});4078});4079});4080describe('cancelable ajax requests', function() {4081var term = $('<div/>').terminal({}, {greetings: false});4082AJAXMock('/500ms', 'foo bar', {async: 500});4083it('should cancel ajax request', function(done) {4084var test = {4085fn: function() {4086}4087};4088spy(test, 'fn');4089term.focus().pause();4090$.get('/500ms', function(data) {4091test.fn(data);4092});4093shortcut(true, false, false, 'D');4094expect(term.paused()).toBeFalsy();4095setTimeout(function() {4096expect(output(term)).toEqual([]);4097expect(test.fn).not.toHaveBeenCalled();4098done();4099}, 600);4100});4101});4102describe('nested object interpreter', function() {4103var interpereter, type, fallback, term;4104beforeEach(function() {4105fallback = {4106interpreter: function(command, term) { }4107};4108interpereter = {4109foo: {4110bar: {4111baz: function() {4112},4113add: function(a, b) {4114this.echo(a+b);4115},4116type: function(obj) {4117type.test(obj.constructor);4118this.echo(JSON.stringify([].slice.call(arguments)));4119}4120}4121},4122quux: '/test'4123};4124type = {4125test: function(obj) { }4126};4127term = $('<div/>').appendTo('body').terminal(interpereter);4128term.focus();4129});4130afterEach(function() {4131term.destroy().remove();4132term = null;4133});4134it('should created nested intepreter', function() {4135term.focus();4136var spy = spyOn(interpereter.foo.bar, 'baz');4137enter(term, 'foo');4138expect(term.get_prompt()).toEqual('foo> ');4139enter(term, 'bar');4140expect(term.get_prompt()).toEqual('bar> ');4141enter(term, 'baz');4142expect(interpereter.foo.bar.baz).toHaveBeenCalled();4143});4144it('should convert arguments', function() {4145spy(type, 'test');4146term.exec(['foo', 'bar']);4147term.insert('add 10 20');4148enter_key();4149var last_div = term.find('.terminal-output > div:last-child');4150expect(last_div.text()).toEqual('30');4151enter(term, 'type /foo/gi');4152expect(type.test).toHaveBeenCalledWith(RegExp);4153enter(term, 'type 10');4154expect(type.test).toHaveBeenCalledWith(Number);4155});4156it('should show error on wrong arity', function() {4157term.exec(['foo', 'bar']);4158enter(term, 'type 10 20');4159var last_div = term.find('.terminal-output > div:last-child');4160expect(last_div.text()).toEqual("[Arity] Wrong number of arguments." +4161" Function 'type' expects 1 got 2!");4162});4163it('should call fallback function', function() {4164spy(fallback, 'interpreter');4165term.destroy().remove();4166term = $('<div/>').appendTo('body').terminal([4167interpereter, fallback.interpreter4168], {4169checkArity: false4170});4171enter(term, 'baz');4172expect(fallback.interpreter).toHaveBeenCalledWith('baz', term);4173});4174it('should not show error on wrong arity', function() {4175term.destroy().remove();4176term = $('<div/>').appendTo('body').terminal([4177interpereter, fallback.interpreter4178], {4179checkArity: false4180});4181spy(type, 'test');4182enter(term, 'foo');4183enter(term, 'bar');4184enter(term, 'type 10 20');4185expect(type.test).toHaveBeenCalled();4186});4187it('should call json-rpc', function() {4188spy(object, 'echo');4189enter(term, 'quux');4190expect(term.get_prompt()).toEqual('quux> ');4191// for unknown reason cmd have visibility set to hidden4192term.cmd().enable().visible();4193enter(term, 'echo foo bar');4194expect(object.echo).toHaveBeenCalledWith('foo', 'bar');4195var new_term = $('<div/>').appendTo('body').terminal([4196interpereter, '/test', fallback.interpreter4197]);4198new_term.focus();4199enter(new_term, 'echo TOKEN world'); // we call echo without login4200expect(object.echo).toHaveBeenCalledWith('TOKEN', 'world');4201});4202it('should show error', function() {4203term.clear();4204enter(term, 'quux');4205enter(term, 'exception TOKEN');4206var last_div = term.find('.terminal-output > div:last-child');4207var out = output(term);4208expect(out[3]).toEqual(' Syntax Error in file "baz.php" at line 10');4209expect(out[3].replace(/^(\s+)[^\s].*/, '$1').length).toEqual(4);4210expect(out).toEqual([4211'> quux',4212'quux> exception TOKEN',4213'[RPC] ' +exception,4214' Syntax Error in file "baz.php" at line 10'4215]);4216});4217it('should show parse error on unclosed string', function() {4218var commands = [4219'foo foo"',4220'foo "foo\\"foo',4221"foo 'foo",4222"foo 'foo\\'foo",4223'foo "foo\\\\\\"foo'4224];4225commands.forEach(function(command) {4226term.focus().clear();4227enter(term, command);4228expect(output(term)).toEqual([4229'> ' + command,4230'Error: Command `' + command + '` have unclosed strings'4231]);4232});4233});4234});4235describe('jQuery Terminal object', function() {4236var test = {4237test: function(term) {}4238};4239var term = $('<div/>').appendTo('body').terminal([{4240foo: function() {4241test.test(this);4242}4243}, function(cmd, term) {4244test.test(term);4245}]);4246it('value returned by plugin should be the same as in intepreter', function() {4247term.focus();4248var spy = spyOn(test, 'test');4249enter(term, 'foo');4250expect(test.test).toHaveBeenCalledWith(term);4251enter(term, 'bar');4252expect(test.test).toHaveBeenCalledWith(term);4253term.destroy().remove();4254});4255});4256describe('Completion', function() {4257var term = $('<div/>').appendTo('body').terminal($.noop, {4258name: 'completion',4259greetings: false,4260completion: ['foo', 'bar', 'baz', 'lorem ipsum']4261});4262it('should complete text for main intepreter', function() {4263term.focus();4264term.insert('f');4265shortcut(false, false, false, 9, 'tab');4266expect(term.get_command()).toEqual('foo');4267term.set_command('');4268term.insert('lorem\\ ');4269shortcut(false, false, false, 9, 'tab');4270expect(term.get_command()).toEqual('lorem\\ ipsum');4271});4272it('should complete text for nested intepreter', function() {4273term.push($.noop, {4274completion: ['lorem', 'ipsum', 'dolor']4275});4276term.insert('l');4277shortcut(false, false, false, 9, 'tab');4278expect(term.get_command()).toEqual('lorem');4279});4280it('should complete when completion is a function with setTimeout', function(done) {4281var term = $('<div/>').appendTo('body').terminal($.noop);4282term.push($.noop, {4283completion: function(string, callback) {4284setTimeout(function() {4285callback(['one', 'two', 'tree']);4286}, 100);4287}4288});4289term.set_command('');4290term.insert('o').focus();4291shortcut(false, false, false, 9, 'tab');4292setTimeout(function() {4293expect(term.get_command()).toEqual('one');4294term.destroy().remove();4295done();4296}, 400);4297});4298function completion(string, callback) {4299var command = term.get_command();4300var cmd = $.terminal.parse_command(command);4301var re = new RegExp('^\\s*' + $.terminal.escape_regex(string));4302if (command.match(re)) {4303callback(['foo', 'bar', 'baz', 'lorem ipsum']);4304} else if (cmd.name == 'foo') {4305callback(['one', 'two', 'tree']);4306} else {4307callback(['four', 'five', 'six']);4308}4309}4310it('should complete argument', function() {4311term.focus().push($.noop, {completion: completion});4312term.set_command('');4313term.insert('foo o');4314shortcut(false, false, false, 9, 'tab');4315expect(term.get_command()).toEqual('foo one');4316term.pop();4317});4318it('should complete in the middle of the word', function() {4319term.push($.noop, {completion: completion});4320term.set_command('f one');4321var cmd = term.cmd();4322cmd.position(1);4323shortcut(false, false, false, 9, 'tab');4324expect(term.get_command()).toEqual('foo one');4325var command = 'lorem\\ ip';4326term.set_command(command +' one');4327cmd.position(command.length);4328shortcut(false, false, false, 9, 'tab');4329expect(term.get_command()).toEqual('lorem\\ ipsum one');4330});4331it('should complete rpc method', function() {4332term.push('/test', {4333completion: true4334});4335term.set_command('').resume().focus();4336term.insert('ec');4337shortcut(false, false, false, 9, 'tab');4338expect(term.get_command()).toEqual('echo');4339});4340it('should complete command from array when used with JSON-RPC', function() {4341term.push('/test', {4342completion: ['foo', 'bar', 'baz']4343});4344term.focus().resume().set_command('');4345term.insert('f');4346shortcut(false, false, false, 9, 'tab');4347expect(term.get_command()).toEqual('foo');4348});4349it('should insert tab when RPC used without system.describe', function(done) {4350term.push('/no_describe', {4351completion: true4352});4353setTimeout(function() {4354term.focus().set_command('').cmd().enable().visible();4355term.insert('f');4356shortcut(false, false, false, 9, 'tab');4357expect(term.get_command()).toEqual('f\t');4358term.destroy().remove();4359done();4360}, 200);4361});4362it('should insert tab when describe === false', function() {4363term = $('<div/>').appendTo('body').terminal('/test', {4364describe: false,4365completion: true4366});4367term.insert('f');4368shortcut(false, false, false, 9, 'tab');4369expect(term.get_command()).toEqual('f\t');4370term.destroy().remove();4371});4372it('should not complete by default for json-rpc', function() {4373term = $('<div/>').appendTo('body').terminal('/test');4374term.focus();4375term.insert('ec');4376shortcut(false, false, false, 9, 'tab');4377expect(term.get_command()).toEqual('ec\t');4378term.destroy().remove();4379});4380it('should complete text with spaces inside quotes', function() {4381term = $('<div/>').appendTo('body').terminal({}, {4382completion: ['foo bar baz']4383});4384term.focus();4385term.insert('asd foo\\ b');4386shortcut(false, false, false, 9, 'tab');4387expect(term.get_command()).toEqual('asd foo\\ bar\\ baz');4388term.destroy().remove();4389});4390it('should complete text that have spaces inside double quote', function() {4391term = $('<div/>').appendTo('body').terminal({}, {4392completion: ['foo bar baz']4393});4394term.focus();4395term.insert('asd "foo b');4396shortcut(false, false, false, 9, 'tab');4397expect(term.get_command()).toEqual('asd "foo bar baz"');4398term.destroy().remove();4399});4400it('should complete special regex characters', function() {4401term = $('<div/>').appendTo('body').terminal({}, {4402completion: ['(macroexpand', '(macroexpand-1', '[regex]', '{tag}'],4403greetings: '',4404completionEscape: false4405});4406return new Promise((resolve) => {4407var specs = [4408['(macro', '(macroexpand'],4409['[reg', '[regex]'],4410['{ta', '{tag}']4411];4412(function loop() {4413var spec = specs.pop();4414if (!spec) {4415resolve();4416} else {4417var [input, output] = spec;4418term.set_command('').insert(input).focus();4419shortcut(false, false, false, 9, 'tab');4420delay(100, () => {4421expect(term.get_output()).toEqual('');4422expect(term.get_command()).toEqual(output);4423loop();4424});4425}4426})();4427});4428term.destroy().remove();4429});4430it('should complete when text have escaped quotes', function() {4431term = $('<div/>').appendTo('body').terminal({}, {4432completion: ['foo "bar" baz']4433});4434term.focus();4435term.insert('asd "foo');4436shortcut(false, false, false, 9, 'tab');4437expect(term.get_command()).toEqual('asd "foo \\"bar\\" baz"');4438term.destroy().remove();4439});4440it('should complete when text have double quote inside single quotes', function() {4441term = $('<div/>').appendTo('body').terminal({}, {4442completion: ['foo "bar" baz']4443});4444term.focus();4445term.insert("asd 'foo");4446shortcut(false, false, false, 9, 'tab');4447expect(term.get_command()).toEqual("asd 'foo \"bar\" baz'");4448term.destroy().remove();4449});4450it('should complete when text have single quote inside double quotes', function() {4451term = $('<div/>').appendTo('body').terminal({}, {4452completion: ["foo 'bar' baz"]4453});4454term.focus();4455term.insert('asd "foo');4456shortcut(false, false, false, 9, 'tab');4457expect(term.get_command()).toEqual("asd \"foo 'bar' baz\"");4458term.destroy().remove();4459});4460it('should complete when function returm promise', function(done) {4461term = $('<div/>').appendTo('body').terminal({}, {4462completion: function() {4463return new Promise(function(resolve) {4464setTimeout(function() {4465resolve(["foo", "bar", "baz"]);4466}, 200);4467});4468}4469});4470term.focus();4471term.insert('f');4472shortcut(false, false, false, 9, 'tab');4473setTimeout(function() {4474expect(term.get_command()).toEqual("foo");4475done();4476}, 400);4477});4478});4479describe('jQuery Terminal methods', function() {4480describe('generic', function() {4481var terminal_name = 'methods';4482var greetings = 'Hello World!';4483var completion = ['foo', 'bar', 'baz'];4484var exported_view;4485var command = 'baz';4486var prompt = '$ ';4487var mask = '-';4488var position;4489var last_id;4490var term;4491beforeEach(function() {4492last_id = $.terminal.last_id();4493term = $('<div/>').appendTo('body').terminal($.noop, {4494name: terminal_name,4495greetings: greetings,4496completion: completion4497});4498});4499afterEach(function() {4500term.destroy().remove();4501});4502it('should return id of the terminal', function() {4503expect(term.id()).toEqual(last_id + 1);4504term.focus();4505});4506it('should clear the terminal', function() {4507term.clear();4508expect(term.find('.terminal-output').html()).toEqual('');4509});4510it('should save commands in hash', function() {4511location.hash = '';4512term.save_state(); // initial state4513term.save_state('foo');4514term.save_state('bar');4515var id = term.id();4516var hash = '#' + JSON.stringify([[id,1,"foo"],[id,2,"bar"]]);4517expect(decodeURIComponent(location.hash)).toEqual(hash);4518});4519describe('import/export view', function() {4520var term = $('<div/>').appendTo('body').terminal($.noop, {4521name: terminal_name,4522greetings: greetings,4523completion: completion4524});4525it('should export view', function() {4526enter(term, 'foo');4527enter(term, 'bar');4528term.insert(command);4529term.set_prompt(prompt);4530term.set_mask(mask);4531position = term.cmd().position();4532exported_view = term.export_view();4533expect(exported_view.prompt).toEqual(prompt);4534expect(exported_view.position).toEqual(position);4535expect(exported_view.focus).toEqual(true);4536expect(exported_view.mask).toEqual(mask);4537expect(exported_view.command).toEqual(command);4538expect(exported_view.lines[0][0]).toEqual('Hello World!');4539expect(exported_view.lines[1][0]).toEqual('> foo');4540expect(exported_view.lines[2][0]).toEqual('> bar');4541expect(exported_view.interpreters.size()).toEqual(1);4542var top = exported_view.interpreters.top();4543expect(top.interpreter).toEqual($.noop);4544expect(top.name).toEqual(terminal_name);4545expect(top.prompt).toEqual(prompt);4546expect(top.greetings).toEqual(greetings);4547expect(top.completion).toEqual('settings');4548});4549it('should import view', function() {4550term.clear().push($.noop).set_prompt('# ')4551.set_mask(false)4552.set_command('foo');4553var cmd = term.cmd();4554cmd.position(0);4555term.import_view(exported_view);4556expect(cmd.mask()).toEqual(mask);4557expect(term.get_command()).toEqual(command);4558expect(term.get_prompt()).toEqual(prompt);4559expect(cmd.position()).toEqual(position);4560var html = '<div data-index="0"><div style="width: 100%;"><span>' +4561'Hello World!</span></div></div><div data-index="1" cla' +4562'ss="terminal-command" role="presentation" aria-hidden="true' +4563'"><div style="width: 100%;"><span>> foo</span></div' +4564'></div><div data-index="2" class="terminal-command" role="p' +4565'resentation" aria-hidden="true"><div style="width: 100%;"><' +4566'span>> bar</span></div></div>';4567expect(term.find('.terminal-output').html()).toEqual(html);4568});4569});4570});4571describe('keymap', function() {4572var term;4573var test;4574beforeEach(function() {4575test = {4576empty: function() {},4577original: function(e, original) {4578original();4579}4580};4581spy(test, 'empty');4582spy(test, 'original');4583term = $('<div/>').terminal($.noop, {4584keymap: {4585'CTRL+C': test.original,4586'CTRL+D': test.original,4587'HOLD+CTRL+I': test.empty,4588'HOLD+CTRL+B': test.empty,4589'HOLD+CTRL+C': test.empty,4590'HOLD+CTRL+D': test.empty4591},4592holdTimeout: 100,4593holdRepeatTimeout: 100,4594repeatTimeoutKeys: ['HOLD+CTRL+B']4595});4596term.focus();4597});4598afterEach(function() {4599term.destroy();4600term = null;4601});4602it('should call init keymap', function() {4603term.clear().insert('foo');4604shortcut(true, false, false, 'c');4605expect(test.original).toHaveBeenCalled();4606expect(last_div(term).text().match(/foo\^C/)).toBeTruthy();4607term.pause();4608shortcut(true, false, false, 'D');4609expect(term.paused()).toBeFalsy();4610});4611it('should set and call keymap shortcut', function() {4612term.keymap('CTRL+T', test.empty);4613shortcut(true, false, false, 84, 't');4614expect(test.empty).toHaveBeenCalled();4615});4616// testing hold key4617function repeat_ctrl_key(key, count) {4618var doc = $(document.documentElement || window);4619var which = key.toUpperCase().charCodeAt(0);4620doc.trigger(keydown(true, false, false, which, key));4621return new Promise(resolve => {4622delay(100, function() {4623(function loop(i) {4624if (i > 0) {4625doc.trigger(keydown(true, false, false, which, key));4626process.nextTick(function() {4627loop(--i);4628});4629} else {4630doc.trigger(keypress(key));4631doc.trigger($.Event("keyup"));4632resolve();4633}4634})(count - 1);4635});4636});4637}4638function ctrl_key_seq(keys) {4639var key = keys[0];4640var doc = $(document.documentElement || window);4641var which = key.toUpperCase().charCodeAt(0);4642doc.trigger(keydown(true, false, false, which, key));4643return new Promise(resolve => {4644delay(100, function() {4645var key;4646(function loop(i) {4647if (keys[i]) {4648key = keys[i];4649var which = key.toUpperCase().charCodeAt(0);4650doc.trigger(keydown(true, false, false, which, key));4651process.nextTick(function() {4652loop(++i);4653});4654} else {4655doc.trigger(keypress(key));4656doc.trigger($.Event("keyup"));4657resolve();4658}4659})(1);4660});4661});4662}4663it('should create new keymap', function() {4664var keys = 5;4665return repeat_ctrl_key('i', keys).then(() => {4666expect(count(test.empty)).toEqual(keys - 1);4667});4668});4669it('should limit rate of repeat keys', function() {4670// testing hold key4671expect(count(test.empty)).toEqual(0);4672return repeat_ctrl_key('b', 5).then(() => {4673return delay(100, function() {4674return repeat_ctrl_key('b', 5).then(() => {4675expect(count(test.empty)).toEqual(2);4676});4677});4678});4679});4680it('should not repeat keys', function() {4681var keys = [];4682for (var i = 0; i < 10; ++i) {4683keys.push('c');4684keys.push('d');4685}4686return ctrl_key_seq(keys).then(() => {4687return delay(100, function() {4688expect(count(test.empty)).toEqual(0);4689expect(count(test.original)).toEqual(keys.length);4690});4691});4692});4693it('should create keymap from object', function() {4694term.keymap({4695'CTRL+T': test.empty4696});4697shortcut(true, false, false, 84, 't');4698expect(test.empty).toHaveBeenCalled();4699});4700it('should overwrite terminal keymap', function() {4701term.echo('foo');4702shortcut(true, false, false, 76, 'l');4703expect(term.get_output()).toEqual('');4704term.echo('bar');4705term.keymap('CTRL+L', test.empty);4706shortcut(true, false, false, 76, 'l');4707expect(term.get_output()).not.toEqual('');4708});4709it('should call default terminal keymap', function() {4710term.echo('foo');4711term.keymap('CTRL+L', test.original);4712shortcut(true, false, false, 76, 'l');4713expect(term.get_output()).toEqual('');4714});4715it('should overwrite cmd keymap', function() {4716term.set_command('foo');4717term.keymap('ENTER', test.empty);4718enter_key();4719expect(term.get_command()).toEqual('foo');4720});4721it('should call default cmd keymap', function() {4722term.set_command('foo');4723term.keymap({4724'ENTER': test.original4725});4726enter_key();4727expect(term.get_command()).toEqual('');4728});4729it('should return keymap', function() {4730var fn = function() { };4731var keys = Object.keys(term.keymap());4732expect(term.keymap('CTRL+S')).toBeFalsy();4733term.keymap('CTRL+S', fn);4734expect(term.keymap('CTRL+S')).toBeTruthy();4735expect(term.keymap()['CTRL+S']).toBeTruthy();4736expect(Object.keys(term.keymap())).toEqual(keys.concat(['CTRL+S']));4737});4738});47394740describe('exec', function() {4741var counter = 0;4742var interpreter;4743var term;4744beforeEach(function() {4745interpreter = {4746foo: function() {4747this.pause();4748setTimeout(() => this.echo('Hello ' + counter++).resume(), 50);4749},4750bar: function() {4751var d = $.Deferred();4752setTimeout(function() {4753d.resolve('Foo Bar');4754}, 100);4755return d.promise();4756},4757baz: {4758quux: function() {4759this.echo('quux');4760}4761}4762};4763spy(interpreter, 'foo');4764term = $('<div/>').appendTo('body').terminal(interpreter);4765term.focus();4766});4767afterEach(function() {4768term.destroy();4769term = null;4770});4771it('should execute function', function() {4772return term.exec('foo').then(function() {4773expect(interpreter.foo).toHaveBeenCalled();4774});4775});4776it('should echo text when promise is resolved', function() {4777return term.exec('bar').then(function() {4778var last_div = term.find('.terminal-output > div:last-child');4779expect(last_div.text()).toEqual('Foo Bar');4780});4781});4782it('should create nested interpreter', function() {4783return term.exec('baz').then(function() {4784expect(term.get_prompt()).toEqual('baz> ');4785return term.exec('quux').then(function() {4786var last_div = term.find('.terminal-output > div:last-child');4787expect(last_div.text()).toEqual('quux');4788});4789});4790});4791var arr = [];4792for (var i = 0; i<10; i++) {4793arr.push('Hello ' + i);4794}4795var test_str = arr.join('\n');4796function text_echoed() {4797return term.find('.terminal-output > div:not(.terminal-command)')4798.map(function() {4799return $(this).text();4800}).get().join('\n');4801}4802it('should execute functions in order when using exec.then', function() {4803term.clear();4804counter = 0;4805var i = 0;4806return new Promise(function(resolve) {4807(function recur() {4808if (i++ < 10) {4809term.exec('foo').then(recur);4810} else {4811expect(text_echoed()).toEqual(test_str);4812resolve();4813}4814})();4815});4816});4817it('should execute functions in order when used delayed commands', function() {4818term.clear();4819counter = 0;4820var promises = [];4821for (var i = 0; i<10; i++) {4822promises.push(term.exec('foo'));4823}4824return Promise.all(promises).then(function() {4825expect(text_echoed()).toEqual(test_str);4826});4827});4828it('should execute array', function() {4829term.clear();4830counter = 0;4831var array = [];4832for (var i = 0; i<10; i++) {4833array.push('foo');4834}4835return term.exec(array).then(function() {4836expect(text_echoed()).toEqual(test_str);4837});4838});4839it('should login from exec array', function() {4840var test = {4841test: function() {4842}4843};4844var token = 'TOKEN';4845var options = {4846login: function(user, password, callback) {4847if (user == 'foo' && password == 'bar') {4848callback(token);4849} else {4850callback(null);4851}4852},4853name: 'exec_login_array',4854greetings: false4855};48564857spy(test, 'test');4858spy(options, 'login');4859var term = $('<div/>').terminal({4860echo: function(arg) {4861test.test(arg);4862}4863}, options);4864if (term.token()) {4865term.logout();4866}4867var array = ['foo', 'bar', 'echo foo'];4868return term.exec(array).then(function() {4869expect(options.login).toHaveBeenCalled();4870expect(test.test).toHaveBeenCalledWith('foo');4871});4872});4873it('should login from hash', function() {4874var test = {4875test: function() {4876}4877};4878var token = 'TOKEN';4879var options = {4880login: function(user, password, callback) {4881if (user == 'foo' && password == 'bar') {4882callback(token);4883} else {4884callback(null);4885}4886},4887name: 'exec_login_array',4888execHash: true,4889greetings: 'exec'4890};4891var next_id = $.terminal.last_id() + 1;4892location.hash = '#' + JSON.stringify([4893[next_id,1,"foo"],4894[next_id,2,"bar"],4895[next_id,3,"echo foo"]4896]);4897spy(test, 'test');4898spy(options, 'login');4899var term = $('<div/>').terminal({4900echo: function(arg) {4901test.test(arg);4902}4903}, options);4904if (term.token()) {4905term.logout();4906}4907return new Promise((resolve, reject) => {4908setTimeout(() => {4909try {4910expect(options.login).toHaveBeenCalled();4911expect(test.test).toHaveBeenCalledWith('foo');4912term.logout();4913resolve();4914} catch (e) {4915reject(e);4916}4917}, 500);4918});4919});4920it('should exec when paused', function() {4921var test = {4922fn: function() {4923}4924};4925spy(test, 'fn');4926var term = $('<div/>').terminal({4927echo: function(arg) {4928test.fn(arg);4929}4930}, {});4931term.pause();4932term.exec('echo foo');4933expect(test.fn).not.toHaveBeenCalled();4934term.resume();4935return new Promise((resolve) => {4936setTimeout(function() {4937expect(test.fn).toHaveBeenCalledWith('foo');4938resolve();4939}, 10);4940});4941});4942it('should throw exception from exec', function() {4943var error = {4944message: 'Some Error',4945stack: 'stack trace'4946};4947expect(function() {4948var term = $('<div/>').terminal({4949echo: function(arg) {4950throw error;4951}4952}, {4953greetings: false4954});4955term.focus().exec('echo foo');4956}).toThrow(error);4957});4958it('should call async rpc methods', function() {4959spy(object, 'echo');4960var term = $('<div/>').terminal('/async');4961term.focus().exec('echo foo bar');4962term.insert('foo');4963new Promise((resolve) => {4964setTimeout(function() {4965expect(object.echo).toHaveBeenCalledWith('foo', 'bar');4966expect(term.get_command()).toEqual('foo');4967resolve();4968}, 800);4969});4970});4971});4972describe('autologin', function() {4973var token = 'TOKEN';4974var options = {4975greetings: 'You have been logged in',4976login: function(user, password, callback) {4977if (user == 'demo' && password == 'demo') {4978callback(token);4979} else {4980callback(null);4981}4982}4983};4984var term = $('<div/>').appendTo('body').terminal($.noop, options);4985it('should log in', function() {4986spy(options, 'login');4987term.autologin('user', token);4988expect(options.login).not.toHaveBeenCalled();4989expect(term.token()).toEqual(token);4990var last_div = term.find('.terminal-output > div:last-child');4991expect(last_div.text()).toEqual('You have been logged in');4992});4993});4994describe('login', function() {4995var term = $('<div/>').appendTo('body').terminal($.noop, {4996name: 'login_method',4997greetings: 'You have been logged in'4998});4999var token = 'TOKEN';5000var login = {5001callback: function(user, password, callback) {5002if (user === 'foo' && password == 'bar') {5003callback(token);5004} else {5005callback(null);5006}5007}5008};5009it('should not login', function() {5010spy(login, 'callback');5011term.focus().login(login.callback);5012enter(term, 'foo');5013enter(term, 'foo');5014expect(login.callback).toHaveBeenCalled();5015var last_div = term.find('.terminal-output > div:last-child');5016expect(last_div.text()).toEqual('Wrong password!');5017expect(term.get_prompt()).toEqual('> ');5018});5019it('should ask for login/password on wrong user/password', function() {5020term.login(login.callback, true);5021for(var i=0; i<10; i++) {5022enter(term, 'foo');5023expect(term.get_prompt()).toEqual('password: ');5024enter(term, 'foo');5025expect(term.get_prompt()).toEqual('login: ');5026}5027term.pop();5028});5029it('should login after first time', function() {5030term.push($.noop, {prompt: '$$ '}).login(login.callback, true);5031enter(term, 'foo');5032enter(term, 'bar');5033expect(term.token(true)).toEqual(token);5034expect(term.get_prompt()).toEqual('$$ ');5035// logout from interpreter, will call login so we need to pop from login5036// and then from intepreter that was pushed5037term.logout(true).pop().pop();5038});5039it('should login after second time', function() {5040term.push($.noop, {prompt: '>>> '}).login(login.callback, true);5041if (term.token(true)) {5042term.logout(true);5043}5044enter(term, 'foo');5045enter(term, 'foo');5046expect(term.token(true)).toBeFalsy();5047enter(term, 'foo');5048enter(term, 'bar');5049expect(term.token(true)).toEqual(token);5050expect(term.get_prompt()).toEqual('>>> ');5051term.logout(true).pop().pop();5052});5053it('should login to nested interpreter when using login option', function() {5054term.push($.noop, {5055prompt: '$$$ ',5056name: 'option',5057login: login.callback,5058infiniteLogin: true5059});5060if (term.token(true)) {5061term.logout(true);5062}5063enter(term, 'foo');5064enter(term, 'foo');5065expect(term.token(true)).toBeFalsy();5066enter(term, 'foo');5067enter(term, 'bar');5068expect(term.token(true)).toEqual(token);5069expect(term.get_prompt()).toEqual('$$$ ');5070});5071});5072describe('settings', function() {5073var term = $('<div/>').appendTo('body').terminal();5074it('should return settings even when option is not defined', function() {5075var settings = term.settings();5076expect($.isPlainObject(settings)).toEqual(true);5077term.destroy().remove();5078for (var key in settings) {5079// name is selector if not defined5080if (settings.hasOwnProperty(key) &&5081!['name', 'exit', 'keymap', 'echoCommand'].includes(key)) {5082// without name and exit + exeptions in newline5083expect([key, $.terminal.defaults[key]]).toEqual([key, settings[key]]);5084}5085}5086});5087});5088describe('commands', function() {5089function interpreter(command, term) {}5090it('should return function', function() {5091var term = $('<div/>').appendTo('body').terminal(interpreter);5092expect(term.commands()).toEqual(interpreter);5093term.push('/test');5094expect($.isFunction(term.commands())).toEqual(true);5095term.destroy().remove();5096});5097});5098// this test long to fix, this function should be not used since it's flacky5099// and it don't return promise when interpreter is created5100describe('set_interpreter', function() {5101var term = $('<div/>').appendTo('body').terminal($.noop);5102it('should change intepreter', function() {5103var test = {5104interpreter: function(command, term) {}5105};5106spy(test, 'interpreter');5107expect(term.commands()).toEqual($.noop);5108term.set_interpreter(test.interpreter);5109expect(term.commands()).toEqual(test.interpreter);5110return term.exec('foo').then(() => {5111expect(test.interpreter).toHaveBeenCalledWith('foo', term);5112});5113});5114it('should create async JSON-RPC with login', function() {5115spy(object, 'echo');5116spy(object, 'login');5117term.set_prompt('$ ');5118term.set_interpreter('/async', true).focus();5119// there seems to be bug in setTimeout in Node or in Terminal code5120// that sometimes don't invoke code when using setTimeout5121return delay(500, () => {5122if (term.token(true)) {5123term.logout(true);5124}5125expect(term.get_prompt()).toEqual('login: ');5126return term.exec(['demo', 'demo']).then(() => {5127expect(term.get_prompt()).toEqual('$ ');5128expect(object.login).toHaveBeenCalledWith('demo', 'demo');5129return delay(50, () => {5130return term.exec('echo foo').then(() => {5131expect(object.echo).toHaveBeenCalledWith(token, 'foo');5132term.destroy().remove();5133});5134});5135});5136});5137});5138});5139describe('greetings', function() {5140it('should show greetings', function(done) {5141var greetings = {5142fn: function(callback) {5143setTimeout(function() {5144callback(greetings.string);5145}, 200);5146},5147string: 'Hello World!'5148};5149spy(greetings, 'fn');5150var term = $('<div/>').terminal($.noop, {5151greetings: greetings.string5152});5153term.clear().greetings();5154var last_div = term.find('.terminal-output > div:last-child');5155expect(last_div.text()).toEqual(nbsp(greetings.string));5156term.settings().greetings = greetings.fn;5157term.clear().greetings();5158expect(greetings.fn).toHaveBeenCalled();5159setTimeout(function() {5160last_div = term.find('.terminal-output > div:last-child');5161expect(last_div.text()).toEqual(nbsp(greetings.string));5162term.settings().greetings = undefined;5163term.clear().greetings();5164last_div = term.find('.terminal-output > div:last-child');5165var text = last_div.find('div').map(function() {5166return $(this).text();5167}).get().join('\n');5168expect(text).toEqual(nbsp(term.signature()));5169term.destroy().remove();5170done();5171}, 400);5172});5173});5174describe('pause/paused/resume', function() {5175var term = $('<div/>').appendTo('body').terminal();5176it('should return true on init', function() {5177expect(term.paused()).toBeFalsy();5178});5179it('should return true when paused', function() {5180term.pause();5181expect(term.paused()).toBeTruthy();5182});5183it('should return false when called resume', function() {5184term.resume();5185expect(term.paused()).toBeFalsy();5186term.destroy().remove();5187});5188});5189describe('cols/rows', function() {5190var numChars = 100;5191var numRows = 25;5192var term = $('<div/>').appendTo('body').terminal($.noop, {5193numChars: numChars,5194numRows: numRows5195});5196it('should return number of cols', function() {5197expect(term.cols()).toEqual(numChars);5198});5199it('should return number of rows', function() {5200expect(term.rows()).toEqual(numRows);5201term.destroy().remove();5202});5203});5204describe('history', function() {5205var term = $('<div/>').appendTo('body').terminal($.noop, {5206name: 'history'5207});5208var history;5209it('should return history object', function() {5210history = term.history();5211expect(history).toEqual(jasmine.any(Object));5212});5213it('should have entered commands', function() {5214history.clear();5215term.focus();5216enter(term, 'foo');5217enter(term, 'bar');5218enter(term, 'baz');5219expect(history.data()).toEqual(['foo', 'bar', 'baz']);5220term.destroy().remove();5221});5222});5223describe('history_state', function() {5224var term = $('<div/>').appendTo('body').terminal($.noop);5225term.echo('history_state');5226it('should not record commands', function() {5227var hash = decodeURIComponent(location.hash);5228term.focus();5229enter(term, 'foo');5230expect(decodeURIComponent(location.hash)).toEqual(hash);5231});5232it('should start recording commands', function(done) {5233location.hash = '';5234term.clear_history_state().clear();5235var id = term.id();5236window.id = id;5237var hash = '#[['+id+',1,"foo"],['+id+',2,"bar"]]';5238term.history_state(true);5239// historyState option is turn on after 1 miliseconds to prevent5240// command, that's enabled the history, to be included in hash5241setTimeout(function() {5242term.focus();5243//delete window.commands;5244enter(term, 'foo');5245enter(term, 'bar');5246setTimeout(function() {5247expect(term.get_output()).toEqual('> foo\n> bar');5248expect(decodeURIComponent(location.hash)).toEqual(hash);5249term.destroy().remove();5250done();5251}, 0);5252}, 400);5253});5254});5255describe('next', function() {5256var term1 = $('<div/>').terminal();5257var term2 = $('<div/>').terminal();5258it('should swith to next terminal', function() {5259term1.focus();5260term1.next();5261expect($.terminal.active().id()).toBe(term2.id());5262term1.destroy();5263term2.destroy();5264});5265});5266describe('focus', function() {5267var term1 = $('<div/>').terminal();5268var term2 = $('<div/>').terminal();5269it('should focus on first terminal', function() {5270term1.focus();5271expect($.terminal.active().id()).toBe(term1.id());5272});5273it('should focus on second terminal', function() {5274term1.focus(false);5275expect($.terminal.active().id()).toBe(term2.id());5276term1.destroy();5277term2.destroy();5278});5279});5280describe('freeze/frozen', function() {5281var term = $('<div/>').appendTo('body').terminal();5282it('should accept input', function() {5283term.focus();5284enter_text('foo');5285expect(term.frozen()).toBeFalsy();5286expect(term.get_command()).toEqual('foo');5287});5288it('should be frozen', function() {5289term.set_command('');5290term.freeze(true);5291expect(term.frozen()).toBeTruthy();5292enter_text('bar');5293expect(term.get_command()).toEqual('');5294});5295it('should not enable terminal', function() {5296expect(term.enabled()).toBeFalsy();5297term.enable();5298expect(term.enabled()).toBeFalsy();5299});5300it('should accpet input again', function() {5301term.freeze(false);5302expect(term.frozen()).toBeFalsy();5303enter_text('baz');5304expect(term.get_command()).toEqual('baz');5305term.destroy();5306});5307});5308describe('enable/disable/enabled', function() {5309var term = $('<div/>').appendTo('body').terminal();5310it('terminal should be enabled', function() {5311term.focus();5312expect(term.enabled()).toBeTruthy();5313});5314it('should disable terminal', function() {5315term.disable();5316expect(term.enabled()).toBeFalsy();5317});5318it('should disable command line plugin', function() {5319expect(term.cmd().isenabled()).toBeFalsy();5320});5321it('should enable terminal', function() {5322term.enable();5323expect(term.enabled()).toBeTruthy();5324});5325it('should enable command line plugin', function() {5326expect(term.cmd().isenabled()).toBeTruthy();5327term.destroy().remove();5328});5329});5330describe('signature', function() {5331var term = $('<div/>').terminal($.noop, {5332numChars: 145333});5334function max_length() {5335var lines = term.signature().split('\n');5336return Math.max.apply(null, lines.map(function(line) {5337return line.length;5338}));5339}5340it('should return space', function() {5341expect(term.signature()).toEqual('');5342});5343it('should return proper max length of signature', function() {5344var numbers = {20: 20, 36: 33, 60: 56, 70: 66, 100: 75};5345Object.keys(numbers).forEach(function(numChars) {5346var length = numbers[numChars];5347term.option('numChars', numChars);5348expect(max_length()).toEqual(length);5349});5350term.destroy();5351});5352});5353describe('version', function() {5354var term = $('<div/>').terminal();5355it('should return version', function() {5356expect(term.version()).toEqual($.terminal.version);5357term.destroy();5358});5359});5360// missing methods after version5361describe('flush', function() {5362var term = $('<div/>').terminal($.noop, {greetings: false});5363it('should echo stuff that was called with flush false', function() {5364term.echo('foo', {flush: false});5365term.echo('bar', {flush: false});5366term.echo('baz', {flush: false});5367term.flush();5368expect(term.find('.terminal-output').text()).toEqual('foobarbaz');5369});5370});5371describe('update', function() {5372var term = $('<div/>').terminal($.noop, {greetings: false});5373it('should update terminal output', function() {5374term.echo('Hello');5375term.update(0, 'Hello, World!');5376expect(term.find('.terminal-output').text()).toEqual(nbsp('Hello, World!'));5377term.clear();5378term.echo('Foo');5379term.echo('Bar');5380term.update(-1, 'Baz');5381expect(term.find('.terminal-output').text()).toEqual('FooBaz');5382term.update(-2, 'Lorem');5383term.update(1, 'Ipsum');5384expect(term.find('.terminal-output').text()).toEqual('LoremIpsum');5385});5386});5387describe('last_index', function() {5388var term = $('<div/>').terminal($.noop, {greetings: false});5389it('should return proper index', function() {5390term.echo('Foo');5391term.echo('Bar');5392expect(term.last_index()).toEqual(1);5393function len() {5394return term.find('.terminal-output div div').length;5395}5396term.echo('Baz');5397term.echo('Quux');5398term.echo('Lorem');5399expect(term.last_index()).toEqual(term.find('.terminal-output div div').length-1);5400var last_line = term.find('.terminal-output > div:eq(' + term.last_index() + ') div');5401expect(last_line.text()).toEqual('Lorem');5402});5403});5404describe('echo', function() {5405var numChars = 100;5406var numRows = 25;5407var term = $('<div/>').appendTo('body').terminal($.noop, {5408greetings: false,5409numChars: numChars,5410numRows: numRows5411});5412function output(selector = '.terminal-output > div div span') {5413return term.find(selector).map(function() {5414return $(this).text().replace(/\xA0/g, ' ');5415}).get();5416}5417it('should echo format urls', function() {5418term.clear();5419term.echo('foo http://jcubic.pl bar http://jcubic.pl/');5420var div = term.find('.terminal-output > div div');5421expect(div.children().length).toEqual(4);5422var link = div.find('a');5423expect(link.length).toEqual(2);5424expect(link.eq(0).attr('target')).toEqual('_blank');5425expect(link.eq(0).attr('href')).toEqual('http://jcubic.pl');5426expect(link.eq(1).attr('href')).toEqual('http://jcubic.pl/');5427});5428it('should echo html', function() {5429var html = [5430'<img src="http://lorempixel.com/300/200/cats/">',5431'<p><strong>hello</strong></p>'5432];5433html.forEach(function(html) {5434term.echo(html, {raw: true});5435var line = term.find('.terminal-output > div:eq(' + term.last_index() + ') div');5436expect(line.html()).toEqual(html);5437});5438});5439it('should call finalize with container div', function() {5440var element;5441var options = {5442finalize: function(div) {5443element = div;5444}5445};5446spy(options, 'finalize');5447term.echo('Lorem Ipsum', options);5448expect(options.finalize).toHaveBeenCalled();5449var line = term.find('.terminal-output > div:eq(' + term.last_index() + ')');5450expect(element.is(line)).toBeTruthy();5451});5452it('should not break words', function() {5453var line = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ultrices rhoncus hendrerit. Nunc ligula eros, tincidunt posuere tristique quis, iaculis non elit.';5454var lines = ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ultrices rhoncus hendrerit. Nunc', 'ligula eros, tincidunt posuere tristique quis, iaculis non elit.'];5455term.clear().echo(line, {keepWords: true});5456expect(output()).toEqual(lines);5457});5458it('should strip whitespace', function() {5459var words = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];5460var input = [];5461var i;5462for (i = 0; i < 30; i++) {5463input.push(words[Math.floor(Math.random() * words.length)]);5464}5465term.clear();5466term.echo(input.join('\t'), {keepWords: true});5467for (i = 80; i < 200; i+=10) {5468term.option('numChars', i);5469output().forEach(function(line) {5470expect(line.match(/^\s+|\s+$/)).toBeFalsy();5471});5472}5473term.option('numChars', numChars);5474});5475it('should break words if words are longer then the line', function() {5476var line = 'MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM';5477var lines = ['MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM', 'MMMMMMMMMMMMMMMMMMMM'];5478term.clear().echo(line);5479expect(output()).toEqual(lines);5480term.clear().echo(line, {keepWords: true});5481expect(output()).toEqual(lines);5482});5483it('should echo both lines if one was not flushed', function() {5484term.clear();5485term.echo('foo', {flush: false});5486term.echo('bar');5487expect(term.find('.terminal-output').text()).toEqual('foobar');5488});5489it('should render multiline JSON array #375', function() {5490var input = JSON.stringify([{"error": "bug"}], null, 2);5491term.clear().echo(input);5492expect(output().join('\n')).toEqual(input);5493});5494it('should print undefined', function() {5495term.clear().echo(undefined);5496expect(output().join('\n')).toEqual('undefined');5497});5498it('should print empty line', function() {5499function test() {5500expect(output('.terminal-output > div div')).toEqual(['']);5501}5502term.clear().echo();5503test();5504term.clear().echo('');5505test();5506});5507it('should align tabs', function() {5508var tests = [5509[5510'foobar\tbar\tbaz\nf\t\tb\tbaz\nfa\t\tba\tbr',5511'foobar bar baz\nf b baz\nfa ba br'5512],5513[5514'foo\t\tbar\tbaz\nf\t\tb\tbaz\nfa\t\tba\tbr',5515'foo bar baz\nf b baz\nfa ba br'5516],5517[5518'foo\t\tbar\t\tbaz\nfoo\t\tb\t\tbaz\nfoobar\tba\t\tbr',5519'foo bar baz\nfoo b baz\nfoobar ba br'5520],5521[5522'\u263a\ufe0foo\t\tbar\t\t\u263a\ufe0faz\nfoo\t\tb\t\tbaz\nfoobar\tba\t\tbr',5523'\u263a\ufe0foo bar \u263a\ufe0faz\nfoo b baz\nfoobar ba br'5524],5525[5526'\u263a\ufe0foo\t\tbar\t\t\u263a\ufe0fa\u0038\ufe0f\u20e3\nfoo\t\tb\t\tbaz\nfoobar\tba\t\tbr',5527'\u263a\ufe0foo bar \u263a\ufe0fa\u0038\ufe0f\u20e3\nfoo b baz\nfoobar ba br'5528]5529];5530tests.forEach(function(test) {5531term.clear().echo(test[0]);5532expect(output().join('\n')).toEqual(test[1]);5533});5534});5535describe('extended commands', function() {5536var term = $('<div/>').terminal($.noop, {5537checkArity: false,5538invokeMethods: true5539});5540var interpreter;5541var formatters;5542beforeEach(function() {5543interpreter = {5544foo: function(a, b) {5545}5546};5547formatters = $.terminal.defaults.formatters;5548$.terminal.defaults.formatters = [5549[/\x1bfoo/g, '[[ foo ]]']5550];5551term.exec('xxxxxxxxxxxxxxxxxxxxxx'); // reset prev exec5552spy(interpreter, 'foo');5553term.push(interpreter).clear();5554});5555afterEach(function() {5556term.pop();5557$.terminal.defaults.formatters = formatters;5558});5559it('should invoke command using formatter', function() {5560term.echo('\x1bfoo');5561expect(interpreter.foo).toHaveBeenCalled();5562});5563it('should invoke command', function() {5564term.echo('[[ foo ]]]');5565expect(interpreter.foo).toHaveBeenCalled();5566});5567it('should invoke command with arguments', function() {5568term.echo('[[ foo "a" /xxx/g ]]]');5569expect(interpreter.foo).toHaveBeenCalledWith("a", /xxx/g);5570});5571it('should invoke terminal method', function() {5572spy(term, 'clear');5573term.echo('[[ terminal::clear() ]]');5574expect(term.clear).toHaveBeenCalled();5575});5576it('should invoke terminal method with arguments', function() {5577global.foo = 10;5578term.echo('[[ terminal::echo("xxx", {finalize: function() { foo = 20; }}) ]]');5579expect(global.foo).toEqual(20);5580});5581it('should invoke terminal method where arguments have newlines', function() {5582global.foo = 10;5583term.echo(`[[ terminal::echo("xxx", {5584finalize: function() {5585foo = 20;5586}5587}) ]]`);5588expect(global.foo).toEqual(20);5589});5590it('should invoke cmd method', function() {5591term.echo('[[ cmd::prompt(">>>") ]]');5592expect(term.cmd().prompt()).toEqual('>>>');5593});5594it('should not execute command by typing', function() {5595term.exec('[[ foo ]]');5596expect(interpreter.foo).not.toHaveBeenCalled();5597expect(a0(term.find('.terminal-command').text())).toEqual('> [[ foo ]]');5598});5599it('should not execute command when overwrting exec', function() {5600term.echo('[[ foo ]]', { exec: false });5601expect(interpreter.foo).not.toHaveBeenCalled();5602expect(a0(term.find('.terminal-output > div:last-child').text())).toEqual('[[ foo ]]');5603});5604it('should not execute methods', function() {5605spy(term, 'clear');5606term.echo('bar');5607term.echo('[[ terminal::clear() ]]', { invokeMethods: false });5608term.echo('[[ foo ]]', { invokeMethods: false });5609expect(interpreter.foo).toHaveBeenCalled();5610expect(a0(term.find('.terminal-output').text())).toEqual('bar');5611});5612it('should execute methods by overwriting options', function() {5613var interpreter = {5614foo: function() {}5615};5616var term = $('<div/>').terminal(interpreter, {5617checkArity: false,5618invokeMethods: false,5619greetings: false5620});5621spy(interpreter, 'foo');5622term.echo('[[ foo ]]', { exec: false });5623expect(interpreter.foo).not.toHaveBeenCalled();5624expect(a0(term.find('.terminal-output').text())).toEqual('[[ foo ]]');5625term.echo('[[ foo xx ]]', { exec: true });5626expect(interpreter.foo).toHaveBeenCalledWith('xx');5627spy(term, 'clear');5628expect(term.clear).not.toHaveBeenCalled();5629term.echo('[[ terminal::clear() ]]');5630expect(term.clear).not.toHaveBeenCalled();5631term.echo('[[ terminal::clear() ]]', { exec: true });5632expect(term.clear).not.toHaveBeenCalled();5633term.echo('[[ terminal::clear() ]]', { invokeMethods: true, exec: false });5634expect(term.clear).not.toHaveBeenCalled();5635term.echo('[[ terminal::clear() ]]', { invokeMethods: true });5636expect(term.clear).toHaveBeenCalled();5637});5638it('should show error on recursive exec', function() {5639var interpreter = {5640foo: function() {5641this.echo('[[ foo ]]');5642}5643};5644var term = $('<div/>').terminal(interpreter, {5645greetings: false5646});5647spy(interpreter, 'foo');5648term.exec('foo');5649expect(count(interpreter.foo)).toEqual(1);5650expect(term.find('.terminal-error').length).toEqual(1);5651expect(a0(term.find('.terminal-error').text()))5652.toEqual($.terminal.defaults.strings.recursiveCall);5653term.echo('[[ foo ]]');5654expect(count(interpreter.foo)).toEqual(2);5655expect(term.find('.terminal-error').length).toEqual(2);5656var output = term.find('.terminal-error').map(function() {5657return a0($(this).text());5658}).get();5659expect(output).toEqual([5660$.terminal.defaults.strings.recursiveCall,5661$.terminal.defaults.strings.recursiveCall5662]);5663});5664});5665});5666describe('error', function() {5667var term = $('<div/>').terminal($.noop, {5668greetings: false,5669numChars: 10005670});5671var defaults = {5672raw: false,5673formatters: false5674};5675it('should echo error', function() {5676spy(term, 'echo');5677term.error('Message');5678expect(term.echo).toHaveBeenCalledWith('[[;;;terminal-error]Message]', defaults);5679});5680it('should escape brakets', function() {5681spy(term, 'echo');5682term.clear().error('[[ Message ]]');5683expect(term.echo).toHaveBeenCalledWith('[[;;;terminal-error][[ Message ]]]',5684defaults);5685var span = term.find('.terminal-output span');5686expect(span.length).toEqual(1);5687expect(span.hasClass('terminal-error')).toBeTruthy();5688});5689it('should handle url', function() {5690term.clear().error('foo https://jcubic.pl bar');5691var children = term.find('.terminal-output div div').children();5692children.filter('span').each(function() {5693expect($(this).hasClass('terminal-error')).toBeTruthy();5694});5695expect(children.filter('a').hasClass('terminal-error')).toBeFalsy();5696expect(term.find('.terminal-output a').attr('href')).toEqual('https://jcubic.pl');5697});5698it('should call finialize', function() {5699var options = {5700finalize: $.noop5701};5702spy(options, 'finalize');5703term.clear().error('Message', options);5704expect(options.finalize).toHaveBeenCalled();5705});5706it('should call echo without raw option', function() {5707spy(term, 'echo');5708var options = {5709finalize: $.noop,5710raw: true,5711flush: true,5712keepWords: false,5713formatters: false5714};5715term.clear().error('Message', options);5716options.raw = false;5717expect(term.echo).toHaveBeenCalledWith('[[;;;terminal-error]Message]', options);57185719});5720});5721describe('exception', function() {5722var error = new Error('Some Message');5723var term;5724var lines = [5725'function foo(a, b) {',5726' return a + b;',5727'}',5728'foo(10, 20);'5729];5730AJAXMock('http://localhost/file.js', lines.join('\n'));5731beforeEach(function() {5732term = $('<div/>').terminal($.noop, {5733greetings: false5734});5735if (error.stack) {5736var length = Math.max.apply(Math, error.stack.split("\n").map(function(line) {5737return line.length;5738}));5739term.option('numChars', length+1);5740}5741});5742afterEach(function() {5743term.destroy().remove();5744});5745it('should show exception', function() {5746term.exception(error, 'ERROR');5747var message = '[[;;;terminal-error][ERROR]: ';5748if (error.fileName) {5749message += ']' + error.fileName + '[[;;;terminal-error]: ' + error.message;5750} else {5751message += error.message;5752}5753message += ']';5754window.message = message;5755var re = new RegExp('^' + $.terminal.escape_regex(message));5756window.term = term;5757expect(term.get_output().match(re)).toBeTruthy();5758var div = term.find('.terminal-output > div:eq(0)');5759expect(div.hasClass('terminal-exception')).toBeTruthy();5760expect(div.hasClass('terminal-message')).toBeTruthy();5761if (error.stack) {5762var output = term.find('.terminal-output div div').map(function() {5763return $(this).text().replace(/\xA0/g, ' ');5764}).get().slice(1);5765expect(error.stack).toEqual(output.join('\n'));5766div = term.find('.terminal-output > div:eq(1)');5767expect(div.hasClass('terminal-exception')).toBeTruthy();5768expect(div.hasClass('terminal-stack-trace')).toBeTruthy();5769}5770});5771it('should fetch line from file using AJAX', function(done) {5772var error = {5773message: 'Some Message',5774fileName: 'http://localhost/file.js',5775lineNumber: 25776};5777term.clear().exception(error, 'foo');5778setTimeout(function() {5779expect(output(term)).toEqual([5780'[foo]: http://localhost/file.js: Some Message',5781'[2]: return a + b;'5782]);5783done();5784}, 100);5785});5786it('should display stack and fetch line from file', function(done) {5787var error = {5788message: 'Some Message',5789filename: 'http://localhost/file.js',5790stack: [5791' foo http://localhost/file.js:2:0',5792' main http://localhost/file.js:4:0'5793].join('\n')5794};5795function output(div) {5796return div.find('div').map(function() {5797return $(this).text();5798}).get().join('\n');5799}5800term.clear().exception(error, 'foo');5801var stack = term.find('.terminal-exception.terminal-stack-trace');5802expect(stack.length).toEqual(1);5803expect(output(stack)).toEqual(nbsp(error.stack));5804stack.find('a').eq(1).click();5805setTimeout(function() {5806expect(stack.next().text()).toEqual(error.filename);5807var code = lines.map(function(line, i) {5808return '[' + (i + 1) + ']: ' + line;5809}).slice(1).join('\n');5810expect(output(stack.next().next())).toEqual(nbsp(code));5811done();5812}, 10);5813});5814});5815describe('scroll', function() {5816var term = $('<div/>').terminal($.noop, {5817height: 100,5818numRows: 105819});5820it('should change scrollTop', function() {5821for (var i = 0; i < 20; i++) {5822term.echo('text ' + i);5823}5824var pos = term.prop('scrollTop');5825term.scroll(10);5826expect(term.prop('scrollTop')).toEqual(pos + 10);5827term.scroll(-10);5828expect(term.prop('scrollTop')).toEqual(pos);5829});5830});5831describe('logout/token', function() {5832var term;5833beforeEach(function() {5834term = $('<div/>').appendTo('body').terminal($.noop, {5835name: 'logout',5836login: function(user, pass, callback) {5837callback('TOKEN');5838}5839});5840if (term.token()) {5841term.logout();5842}5843term.focus();5844enter(term, 'foo');5845enter(term, 'bar');5846});5847afterEach(function() {5848term.destroy().remove();5849});5850function push_interpreter() {5851term.push({}, {5852prompt: '$ ',5853login: function(user, pass, callback) {5854callback(user == '1' && pass == '1' ? 'TOKEN2' : null);5855}5856});5857if (term.token(true)) {5858term.logout(true);5859}5860}5861it('should logout from main intepreter', function() {5862expect(term.token()).toEqual('TOKEN');5863expect(term.get_prompt()).toEqual('> ');5864term.logout();5865expect(term.get_prompt()).toEqual('login: ');5866});5867it('should logout from nested interpeter', function() {5868push_interpreter();5869enter(term, '1');5870enter(term, '1');5871expect(term.token(true)).toEqual('TOKEN2');5872term.logout(true);5873expect(term.get_prompt()).toEqual('login: ');5874expect(term.token(true)).toBeFalsy();5875enter(term, '1');5876enter(term, '1');5877expect(term.token(true)).toEqual('TOKEN2');5878expect(term.get_prompt()).toEqual('$ ');5879});5880it('should not logout from main intepreter', function() {5881push_interpreter();5882enter(term, '1');5883enter(term, '1');5884expect(term.token(true)).toEqual('TOKEN2');5885term.logout(true);5886expect(term.token()).toEqual('TOKEN');5887});5888it('should throw exception when calling from login', function() {5889term.logout();5890var strings = $.terminal.defaults.strings;5891var error = new Error(sprintf(strings.notWhileLogin, 'logout'));5892expect(function() { term.logout(); }).toThrow(error);5893// in firefox terminal is pausing to fetch the line that trigger exception5894term.option('onResume', function() {5895term.focus();5896enter(term, '1');5897enter(term, '1');5898push_interpreter();5899expect(function() { term.logout(true); }).toThrow(error);5900});5901});5902it('should logout from all interpreters', function() {5903push_interpreter();5904enter(term, '2');5905enter(term, '2');5906push_interpreter();5907enter(term, '2');5908enter(term, '2');5909term.logout();5910expect(term.token()).toBeFalsy();5911expect(term.token(true)).toBeFalsy();5912expect(term.get_prompt()).toEqual('login: ');5913});5914});5915describe('get_token', function() {5916var term = $('<div/>').terminal();5917it('should call token', function() {5918spyOn(term, 'token');5919term.get_token();5920expect(term.token).toHaveBeenCalled();5921});5922});5923describe('login_name', function() {5924var term;5925beforeEach(function() {5926term = $('<div/>').terminal({}, {5927name: 'login_name',5928login: function(user, pass, callback) {5929callback('TOKEN');5930}5931});5932if (!term.token()) {5933term.focus();5934enter(term, 'foo');5935enter(term, 'bar');5936}5937});5938afterEach(function() {5939term.destroy();5940});5941it('should return main login name', function() {5942expect(term.login_name()).toEqual('foo');5943});5944function push_interpeter() {5945term.push({}, {5946name: 'nested',5947login: function(user, pass, callback) {5948callback('TOKEN2');5949}5950});5951if (!term.token(true)) {5952enter(term, 'bar');5953enter(term, 'bar');5954}5955}5956it('should return main login name for nested interpreter', function() {5957push_interpeter();5958expect(term.login_name()).toEqual('foo');5959});5960it('should return nested interpreter name', function() {5961push_interpeter();5962expect(term.login_name(true)).toEqual('bar');5963});5964});5965describe('name', function() {5966var term;5967beforeEach(function() {5968term = $('<div/>').terminal({}, {5969name: 'test_name'5970});5971});5972it('should return terminal name', function() {5973expect(term.name()).toEqual('test_name');5974});5975it('should return nested intepreter name', function() {5976term.push({}, {5977name: 'other_name'5978});5979expect(term.name()).toEqual('other_name');5980});5981});5982describe('prefix_name', function() {5983it('should return terminal id if terminal have no name', function() {5984var term = $('<div/>').terminal();5985expect(term.prefix_name()).toEqual(String(term.id()));5986expect(term.prefix_name(true)).toEqual(String(term.id()));5987});5988it('should return name and terminal id for main interpreter', function() {5989var term = $('<div/>').terminal({}, {5990name: 'test'5991});5992expect(term.prefix_name()).toEqual('test_' + term.id());5993expect(term.prefix_name(true)).toEqual('test_' + term.id());5994});5995it('should return main name for nested interpreter', function() {5996var term = $('<div/>').terminal({}, {5997name: 'test'5998});5999term.push({}, {name: 'test'});6000expect(term.prefix_name()).toEqual('test_' + term.id());6001});6002it('should return name for nested intepters', function() {6003var term = $('<div/>').terminal({}, {6004name: 'test'6005});6006var names = ['foo', 'bar', 'baz'];6007names.forEach(function(name) {6008term.push({}, {name: name});6009});6010expect(term.prefix_name(true)).toEqual('test_' + term.id() + '_' + names.join('_'));6011});6012it('should return name for nested interpreter without names', function() {6013var term = $('<div/>').terminal({}, {6014name: 'test'6015});6016for(var i=0; i<3; ++i) {6017term.push({});6018}6019expect(term.prefix_name(true)).toEqual('test_' + term.id() + '___');6020});6021});6022describe('read', function() {6023var term;6024var test;6025beforeEach(function() {6026term = $('<div/>').terminal();6027});6028afterEach(function() {6029term.destroy();6030});6031it('should call have prompt', function() {6032term.read('text: ');6033expect(term.get_prompt()).toEqual('text: ');6034});6035it('should return promise that get resolved', function() {6036var test = {6037callback: function() {}6038};6039spyOn(test, 'callback');6040var promise = term.read('foo: ', test.callback);6041promise.then(test.callback);6042var text = 'lorem ipsum';6043enter(term, text);6044expect(test.callback).toHaveBeenCalledWith(text);6045});6046it('should call call function with text', function() {6047var test = {6048callback: function() {}6049};6050spyOn(test, 'callback');6051term.read('foo: ', test.callback);6052var text = 'lorem ipsum';6053enter(term, text);6054expect(test.callback).toHaveBeenCalledWith(text);6055});6056it('should cancel', function() {6057var test = {6058success: function() {},6059cancel: function() {}6060};6061spyOn(test, 'success');6062spyOn(test, 'cancel');6063term.read('foo: ', test.success, test.cancel);6064shortcut(true, false, false, 'd');6065expect(test.success).not.toHaveBeenCalled();6066expect(test.cancel).toHaveBeenCalled();6067});6068});6069describe('push', function() {6070var term;6071beforeEach(function() {6072term = $('<div/>').terminal({6073name: function(name) {6074this.push({}, {6075name: name6076});6077},6078no_name: function() {6079this.push({});6080}6081});6082term.focus();6083});6084afterEach(function() {6085term.destroy().remove();6086});6087it('should push new interpreter', function() {6088term.push({});6089expect(term.level()).toEqual(2);6090});6091it('should create name from previous command', function() {6092enter(term, 'name foo');6093expect(term.name()).toEqual('foo');6094});6095it('should create prompt from previous command', function() {6096enter(term, 'no_name');6097expect(term.get_prompt()).toEqual('no_name ');6098});6099it('should create completion', function() {6100term.push({6101foo: function() {},6102bar: function() {},6103baz: function() {}6104}, {6105name: 'push_completion',6106completion: true6107});6108var top = term.export_view().interpreters.top();6109expect(top.name).toEqual('push_completion');6110expect(top.completion).toEqual(['foo', 'bar', 'baz']);6111});6112it('should create login', function() {6113term.push({}, {6114login: function() {}6115});6116expect(term.get_prompt()).toEqual('login: ');6117});6118it('should create login for JSON-RPC', function() {6119spyOn(object, 'login');6120term.push('/test', {6121login: true,6122name: 'push_login_rpc'6123});6124if (term.token(true)) {6125term.logout(true);6126}6127expect(term.get_prompt()).toEqual('login: ');6128enter(term, 'demo');6129enter(term, 'demo');6130expect(object.login).toHaveBeenCalled();6131});6132it('should keep asking for login when infiniteLogin option is set to true', function() {6133var token = 'infiniteLogin_TOKEN';6134var prompt = '>>> ';6135term.push({}, {6136login: function(user, pass, callback) {6137callback(user == 'foo' && pass == 'bar' ? token : null);6138},6139infiniteLogin: true,6140name: 'infiniteLogin',6141prompt: prompt6142});6143if (term.token(true)) {6144term.logout(true);6145}6146enter(term, 'baz');6147enter(term, 'baz');6148var strings = $.terminal.defaults.strings;6149var error = nbsp(strings.wrongPasswordTryAgain);6150expect(term.find('.terminal-output > div:last-child').text()).toEqual(error);6151expect(term.get_prompt()).toEqual('login: ');6152enter(term, 'foo');6153enter(term, 'bar');6154expect(term.get_token(true)).toEqual(token);6155expect(term.get_prompt()).toEqual(prompt);6156});6157});6158describe('pop', function() {6159describe('with login', function() {6160var token = 'TOKEN';6161var term;6162var options;6163beforeEach(function() {6164options = {6165name: 'pop',6166onExit: function() {},6167login: function(user, password, callback) {6168callback(token);6169},6170onPop: function() {}6171};6172spy(options, 'onExit');6173spy(options, 'onPop');6174term = $('<div/>').terminal({}, options);6175if (term.token()) {6176term.logout();6177}6178enter(term, 'foo');6179enter(term, 'bar');6180['one', 'two', 'three', 'four'].forEach(function(name, index) {6181term.push($.noop, {6182name: name,6183prompt: (index+1) + '> '6184});6185});6186});6187afterEach(function() {6188reset(options.onExit);6189reset(options.onPop);6190term.destroy();6191});6192it('should return terminal object', function() {6193expect(term.pop()).toEqual(term);6194});6195it('should pop one interpreter', function() {6196term.pop();6197expect(term.name()).toEqual('three');6198expect(term.get_prompt()).toEqual('3> ');6199});6200it('should pop all interpreters', function() {6201while(term.level() > 1) {6202term.pop();6203}6204expect(term.name()).toEqual('pop');6205expect(term.get_prompt()).toEqual('> ');6206});6207it('should logout from main intepreter', function() {6208while(term.level() > 1) {6209term.pop();6210}6211term.pop();6212expect(term.get_prompt()).toEqual('login: ');6213});6214it('should call callbacks', function() {6215expect(count(options.onPop)).toBe(0);6216while(term.level() > 1) {6217term.pop();6218}6219term.pop();6220expect(options.onExit).toHaveBeenCalled();6221expect(options.onExit).toHaveBeenCalled();6222expect(count(options.onExit)).toBe(1);6223expect(count(options.onPop)).toBe(5);6224});6225});6226});6227describe('option', function() {6228var options = {6229prompt: '$ ',6230onInit: function() {6231},6232width: 400,6233onPop: function() {6234}6235};6236var term = $('<div/>').terminal($.noop, options);6237it('should return option', function() {6238Object.keys(options).forEach(function(name) {6239expect(term.option(name)).toEqual(options[name]);6240});6241});6242it('should set single value', function() {6243expect(term.option('prompt')).toEqual('$ ');6244term.option('prompt', '>>> ');6245expect(term.option('prompt')).toEqual('>>> ');6246});6247it('should set object', function() {6248var options = {6249prompt: '# ',6250onInit: function() {6251console.log('onInit');6252}6253};6254term.option(options);6255Object.keys(options).forEach(function(name) {6256expect(term.option(name)).toEqual(options[name]);6257});6258});6259});6260describe('level', function() {6261var term = $('<div/>').terminal();6262it('should return proper level name', function() {6263expect(term.level()).toEqual(1);6264term.push($.noop);6265term.push($.noop);6266expect(term.level()).toEqual(3);6267term.pop();6268expect(term.level()).toEqual(2);6269});6270});6271describe('reset', function() {6272var interpreter = function(command) {6273};6274var greetings = 'Hello';6275var term = $('<div/>').terminal(interpreter, {6276prompt: '1> ',6277greetings: greetings6278});6279it('should reset all interpreters', function() {6280term.push($.noop, {prompt: '2> '});6281term.push($.noop, {prompt: '3> '});6282term.push($.noop, {prompt: '4> '});6283expect(term.level()).toEqual(4);6284term.echo('foo');6285term.reset();6286expect(term.level()).toEqual(1);6287expect(term.get_prompt()).toEqual('1> ');6288expect(term.get_output()).toEqual(greetings);6289});6290});6291describe('purge', function() {6292var token = 'purge_TOKEN';6293var password = 'password';6294var username = 'some-user';6295var term;6296beforeEach(function() {6297term = $('<div/>').terminal($.noop, {6298login: function(user, pass, callback) {6299callback(token);6300},6301name: 'purge'6302});6303if (term.token()) {6304term.logout();6305}6306enter(term, username);6307enter(term, password);6308});6309afterEach(function() {6310term.purge().destroy();6311});6312it('should remove login and token', function() {6313expect(term.login_name()).toEqual(username);6314expect(term.token()).toEqual(token);6315term.purge();6316expect(term.login_name()).toBeFalsy();6317expect(term.token()).toBeFalsy();6318});6319it('should remove commands history', function() {6320var commands = ['echo "foo"', 'sleep', 'pause'];6321commands.forEach(function(command) {6322enter(term, command);6323});6324var history = term.history();6325expect(history.data()).toEqual(commands);6326term.purge();6327expect(history.data()).toEqual([]);6328});6329});6330describe('destroy', function() {6331var greetings = 'hello world!';6332var element = '<span>span</span>';6333var term;6334var height = 400;6335var width = 200;6336beforeEach(function() {6337term = $('<div class="foo">' + element + '</div>').terminal($.noop, {6338greetings: greetings,6339width: width,6340height: height6341});6342});6343it('should remove terminal class', function() {6344expect(term.hasClass('terminal')).toBeTruthy();6345term.destroy();6346expect(term.hasClass('terminal')).toBeFalsy();6347expect(term.hasClass('foo')).toBeTruthy();6348});6349it('should remove command line and output', function() {6350term.destroy();6351expect(term.find('.terminal-output').length).toEqual(0);6352expect(term.find('.cmd').length).toEqual(0);6353});6354it('should leave span intact', function() {6355term.destroy();6356expect(term.html()).toEqual(element);6357});6358it('should throw when calling method after destroy', function() {6359term.destroy();6360expect(function() { term.login(); }).toThrow(6361new $.terminal.Exception($.terminal.defaults.strings.defunctTerminal)6362);6363});6364});6365describe('set_token', function() {6366var token = 'set_token';6367var term = $('<div/>').terminal($.noop, {6368login: function(user, password, callback) {6369callback(token);6370}6371});6372if (term.token()) {6373term.logout();6374}6375it('should set token', function() {6376expect(term.token()).toBeFalsy();6377enter(term, 'user');6378enter(term, 'password');6379expect(term.token()).toEqual(token);6380var newToken = 'set_token_new';6381term.set_token(newToken);6382expect(term.token()).toEqual(newToken);6383});6384});6385describe('before_cursor', function() {6386var term = $('<div/>').terminal();6387var cmd = term.cmd();6388it('should return word before cursor', function() {6389var commands = [6390['foo bar baz', 'baz'],6391['foo "bar baz', '"bar baz'],6392["foo \"bar\" 'baz quux", "'baz quux"],6393['foo "foo \\" bar', '"foo \\" bar']6394];6395commands.forEach(function(spec) {6396term.set_command(spec[0]);6397expect(term.before_cursor(true)).toEqual(spec[1]);6398});6399});6400it('should return word before cursor when cursor not at the end', function() {6401var commands = [6402['foo bar baz', 'b'],6403['foo "bar baz', '"bar b'],6404["foo \"bar\" 'baz quux", "'baz qu"],6405['foo "foo \\" bar', '"foo \\" b']6406];6407commands.forEach(function(spec) {6408term.set_command(spec[0]);6409cmd.position(-2, true);6410expect(term.before_cursor(true)).toEqual(spec[1]);6411});6412});6413it('should return text before cursor', function() {6414var command = 'foo bar baz';6415term.set_command(command);6416expect(term.before_cursor()).toEqual(command);6417cmd.position(-2, true);6418expect(term.before_cursor()).toEqual('foo bar b');6419});6420});6421describe('set_command/get_command', function() {6422var term = $('<div/>').terminal($.noop, {6423prompt: 'foo> '6424});6425it('should return init prompt', function() {6426expect(term.get_prompt()).toEqual('foo> ');6427});6428it('should return new prompt', function() {6429var prompt = 'bar> ';6430term.set_prompt(prompt);6431expect(term.get_prompt()).toEqual(prompt);6432});6433});6434describe('complete', function() {6435var term = $('<div/>').terminal();6436function test(specs, options) {6437specs.forEach(function(spec) {6438term.focus();6439// complete method resets tab count when you press non-tab6440shortcut(false, false, false, 37, 'arrowleft');6441term.set_command(spec[0]);6442expect(term.complete(spec[1], options)).toBe(spec[2]);6443expect(term.get_command()).toEqual(spec[3]);6444});6445}6446it('should complete whole word', function() {6447test([6448['f', ['foo', 'bar'], true, 'foo'],6449['b', ['foo', 'bar'], true, 'bar'],6450['F', ['FOO', 'BAR'], true, 'FOO'],6451['f', ['FOO', 'BAR'], undefined, 'f']6452]);6453});6454it('should complete common string', function() {6455test([6456['f', ['foo-bar', 'foo-baz'], true, 'foo-ba'],6457['f', ['fooBar', 'fooBaz'], true, 'fooBa']6458]);6459});6460it("should complete word that don't match case", function() {6461test([6462['f', ['FOO-BAZ', 'FOO-BAR'], true, 'foo-ba'],6463['f', ['FooBar', 'FooBaz'], true, 'fooBa'],6464['Foo', ['FOO-BAZ', 'FOO-BAR'], true, 'Foo-BA'],6465['FOO', ['FOO-BAZ', 'FOO-BAR'], true, 'FOO-BA'],6466], {6467caseSensitive: false6468});6469});6470it('should complete whole line', function() {6471var completion = ['lorem ipsum dolor sit amet', 'foo bar baz'];6472test([6473['"lorem ipsum', completion, true, '"lorem ipsum dolor sit amet"'],6474['lorem\\ ipsum', completion, true, 'lorem\\ ipsum\\ dolor\\ sit\\ amet'],6475]);6476test([6477['lorem', completion, true, completion[0]],6478['lorem ipsum', completion, true, completion[0]]6479], {6480word: false,6481escape: false6482});6483});6484it('should echo all matched strings', function() {6485var completion = ['foo-bar', 'foo-baz', 'foo-quux'];6486term.clear().focus();6487term.set_command('f');6488term.complete(completion, {echo: true});6489term.complete(completion, {echo: true});6490var re = new RegExp('^>\\sfoo-\\n' + completion.join('\\s+') + '$');6491var output = term.find('.terminal-output > div').map(function() {6492return $(this).text();6493}).get().join('\n');6494expect(output.match(re)).toBeTruthy();6495});6496});6497describe('get_output', function() {6498var term = $('<div/>').terminal($.noop, {greetings: false});6499it('should return string out of all lines', function() {6500term.echo('foo');6501term.echo('bar');6502term.focus().set_command('quux');6503enter_key();6504expect(term.get_output()).toEqual('foo\nbar\n> quux');6505});6506});6507describe('update', function() {6508var term = $('<div/>').terminal();6509function last_line() {6510return last_div().find('div');6511}6512function last_div() {6513return term.find('.terminal-output > div:last-child');6514}6515it('should update last line', function() {6516term.echo('foo');6517expect(last_line().text()).toEqual('foo');6518term.update(-1, 'bar');6519expect(last_line().text()).toEqual('bar');6520});6521it('should remove last line', function() {6522var index = term.last_index();6523term.echo('quux');6524expect(term.last_index()).toEqual(index + 1);6525term.update(index + 1, null);6526expect(term.last_index()).toEqual(index);6527});6528it('should call finalize', function() {6529var options = {6530finalize: function() {}6531};6532spy(options, 'finalize');6533term.update(-1, 'baz', options);6534expect(options.finalize).toHaveBeenCalled();6535});6536});6537describe('invoke_key', function() {6538it('should invoke key', function() {6539var term = $('<div/>').terminal();6540expect(term.find('.terminal-output').html()).toBeTruthy();6541term.invoke_key('CTRL+L');6542expect(term.find('.terminal-output').html()).toBeFalsy();6543});6544});6545});6546});654765486549