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