@@ -8,6 +8,7 @@ const REPL = require('internal/repl');
88const assert = require ( 'assert' ) ;
99const fs = require ( 'fs' ) ;
1010const path = require ( 'path' ) ;
11+ const { inspect } = require ( 'util' ) ;
1112
1213const tmpdir = require ( '../common/tmpdir' ) ;
1314tmpdir . refresh ( ) ;
@@ -17,6 +18,7 @@ const defaultHistoryPath = path.join(tmpdir.path, '.node_repl_history');
1718// Create an input stream specialized for testing an array of actions
1819class ActionStream extends stream . Stream {
1920 run ( data ) {
21+ let reallyWait = true ;
2022 const _iter = data [ Symbol . iterator ] ( ) ;
2123 const doAction = ( ) => {
2224 const next = _iter . next ( ) ;
@@ -32,24 +34,34 @@ class ActionStream extends stream.Stream {
3234 } else {
3335 this . emit ( 'data' , `${ action } ` ) ;
3436 }
35- setImmediate ( doAction ) ;
37+ if ( action === WAIT && reallyWait ) {
38+ setTimeout ( doAction , common . platformTimeout ( 50 ) ) ;
39+ reallyWait = false ;
40+ } else {
41+ setImmediate ( doAction ) ;
42+ }
3643 } ;
37- setImmediate ( doAction ) ;
44+ doAction ( ) ;
3845 }
3946 resume ( ) { }
4047 pause ( ) { }
4148}
4249ActionStream . prototype . readable = true ;
4350
44-
4551// Mock keys
4652const ENTER = { name : 'enter' } ;
4753const UP = { name : 'up' } ;
4854const DOWN = { name : 'down' } ;
4955const LEFT = { name : 'left' } ;
56+ const RIGHT = { name : 'right' } ;
5057const DELETE = { name : 'delete' } ;
58+ const BACKSPACE = { name : 'backspace' } ;
59+ const WORD_LEFT = { name : 'left' , ctrl : true } ;
60+ const WORD_RIGHT = { name : 'right' , ctrl : true } ;
61+ const GO_TO_END = { name : 'end' } ;
5162
5263const prompt = '> ' ;
64+ const WAIT = '€' ;
5365
5466const prev = process . features . inspector ;
5567
@@ -91,6 +103,162 @@ const tests = [
91103 ' 2025, 2116, 2209, ...' ,
92104 prompt ] . filter ( ( e ) => typeof e === 'string' ) ,
93105 clean : true
106+ } ,
107+ {
108+ env : { NODE_REPL_HISTORY : defaultHistoryPath } ,
109+ skip : ! process . features . inspector ,
110+ test : [
111+ `const ${ 'veryLongName' . repeat ( 30 ) } = 'I should not be previewed'` ,
112+ ENTER ,
113+ 'const e = new RangeError("visible\\ninvisible")' ,
114+ ENTER ,
115+ 'e' ,
116+ ENTER ,
117+ 'veryLongName' . repeat ( 30 ) ,
118+ ENTER ,
119+ `${ '\x1B[90m \x1B[39m' . repeat ( 235 ) } fun` ,
120+ ENTER ,
121+ `${ ' ' . repeat ( 236 ) } fun` ,
122+ ENTER
123+ ] ,
124+ expected : [ ] ,
125+ clean : false
126+ } ,
127+ {
128+ env : { NODE_REPL_HISTORY : defaultHistoryPath } ,
129+ columns : 250 ,
130+ skip : ! process . features . inspector ,
131+ test : [
132+ UP ,
133+ UP ,
134+ UP ,
135+ UP ,
136+ BACKSPACE
137+ ] ,
138+ expected : [
139+ prompt ,
140+ // This exceeds the maximum columns (250):
141+ // Whitespace + prompt + ' // '.length + 'function'.length
142+ // 236 + 2 + 4 + 8
143+ `${ prompt } ${ ' ' . repeat ( 236 ) } fun` ,
144+ `${ prompt } ${ ' ' . repeat ( 235 ) } fun` ,
145+ ' // ction' ,
146+ ' // ction' ,
147+ `${ prompt } ${ 'veryLongName' . repeat ( 30 ) } ` ,
148+ `${ prompt } e` ,
149+ '\n// RangeError: visible' ,
150+ prompt
151+ ] ,
152+ clean : true
153+ } ,
154+ {
155+ env : { NODE_REPL_HISTORY : defaultHistoryPath } ,
156+ showEscapeCodes : true ,
157+ skip : ! process . features . inspector ,
158+ test : [
159+ 'fun' ,
160+ RIGHT ,
161+ BACKSPACE ,
162+ LEFT ,
163+ LEFT ,
164+ 'A' ,
165+ BACKSPACE ,
166+ GO_TO_END ,
167+ BACKSPACE ,
168+ WORD_LEFT ,
169+ WORD_RIGHT ,
170+ ENTER
171+ ] ,
172+ // C = Cursor forward
173+ // D = Cursor back
174+ // G = Cursor to column n
175+ // J = Erase in screen
176+ // K = Erase in line
177+ expected : [
178+ // 0.
179+ // 'f'
180+ '\x1B[1G' , '\x1B[0J' , prompt , '\x1B[3G' , 'f' ,
181+ // 'u'
182+ 'u' , ' // nction' , '\x1B[5G' ,
183+ // 'n' - Cleanup
184+ '\x1B[0K' ,
185+ 'n' , ' // ction' , '\x1B[6G' ,
186+ // 1. Right. Cleanup
187+ '\x1B[0K' ,
188+ 'ction' ,
189+ // 2. Backspace. Refresh
190+ '\x1B[1G' , '\x1B[0J' , `${ prompt } functio` , '\x1B[10G' ,
191+ // Autocomplete and refresh?
192+ ' // n' , '\x1B[10G' , ' // n' , '\x1B[10G' ,
193+ // 3. Left. Cleanup
194+ '\x1B[0K' ,
195+ '\x1B[1D' , '\x1B[10G' , ' // n' , '\x1B[9G' ,
196+ // 4. Left. Cleanup
197+ '\x1B[10G' , '\x1B[0K' , '\x1B[9G' ,
198+ '\x1B[1D' , '\x1B[10G' , ' // n' , '\x1B[8G' ,
199+ // 5. 'A' - Cleanup
200+ '\x1B[10G' , '\x1B[0K' , '\x1B[8G' ,
201+ // Refresh
202+ '\x1B[1G' , '\x1B[0J' , `${ prompt } functAio` , '\x1B[9G' ,
203+ // 6. Backspace. Refresh
204+ '\x1B[1G' , '\x1B[0J' , `${ prompt } functio` , '\x1B[8G' , '\x1B[10G' , ' // n' ,
205+ '\x1B[8G' , '\x1B[10G' , ' // n' ,
206+ '\x1B[8G' , '\x1B[10G' ,
207+ // 7. Go to end. Cleanup
208+ '\x1B[0K' , '\x1B[8G' , '\x1B[2C' ,
209+ 'n' ,
210+ // 8. Backspace. Refresh
211+ '\x1B[1G' , '\x1B[0J' , `${ prompt } functio` , '\x1B[10G' ,
212+ // Autocomplete
213+ ' // n' , '\x1B[10G' , ' // n' , '\x1B[10G' ,
214+ // 9. Word left. Cleanup
215+ '\x1B[0K' , '\x1B[7D' , '\x1B[10G' , ' // n' , '\x1B[3G' , '\x1B[10G' ,
216+ // 10. Word right. Cleanup
217+ '\x1B[0K' , '\x1B[3G' , '\x1B[7C' , ' // n' , '\x1B[10G' ,
218+ '\x1B[0K' ,
219+ ] ,
220+ clean : true
221+ } ,
222+ {
223+ // Check that the completer ignores completions that are outdated.
224+ env : { NODE_REPL_HISTORY : defaultHistoryPath } ,
225+ completer ( line , callback ) {
226+ if ( line . endsWith ( WAIT ) ) {
227+ setTimeout (
228+ callback ,
229+ common . platformTimeout ( 40 ) ,
230+ null ,
231+ [ [ `${ WAIT } WOW` ] , line ]
232+ ) ;
233+ } else {
234+ callback ( null , [ [ ' Always visible' ] , line ] ) ;
235+ }
236+ } ,
237+ skip : ! process . features . inspector ,
238+ test : [
239+ WAIT , // The first call is awaited before new input is triggered!
240+ BACKSPACE ,
241+ 's' ,
242+ BACKSPACE ,
243+ WAIT , // The second call is not awaited. It won't trigger the preview.
244+ BACKSPACE ,
245+ 's' ,
246+ BACKSPACE
247+ ] ,
248+ expected : [
249+ prompt ,
250+ WAIT ,
251+ ' // WOW' ,
252+ prompt ,
253+ 's' ,
254+ ' // Always visible' ,
255+ prompt ,
256+ WAIT ,
257+ prompt ,
258+ 's' ,
259+ ' // Always visible' ,
260+ ] ,
261+ clean : true
94262 }
95263] ;
96264const numtests = tests . length ;
@@ -112,29 +280,47 @@ function runTest() {
112280 const opts = tests . shift ( ) ;
113281 if ( ! opts ) return ; // All done
114282
115- const env = opts . env ;
116- const test = opts . test ;
117- const expected = opts . expected ;
283+ const { expected, skip } = opts ;
284+
285+ // Test unsupported on platform.
286+ if ( skip ) {
287+ setImmediate ( runTestWrap , true ) ;
288+ return ;
289+ }
290+
291+ const lastChunks = [ ] ;
118292
119- REPL . createInternalRepl ( env , {
293+ REPL . createInternalRepl ( opts . env , {
120294 input : new ActionStream ( ) ,
121295 output : new stream . Writable ( {
122296 write ( chunk , _ , next ) {
123297 const output = chunk . toString ( ) ;
124298
125- if ( output . charCodeAt ( 0 ) === 27 || / ^ [ \r \n ] + $ / . test ( output ) ) {
299+ if ( ! opts . showEscapeCodes &&
300+ output . charCodeAt ( 0 ) === 27 || / ^ [ \r \n ] + $ / . test ( output ) ) {
126301 return next ( ) ;
127302 }
128303
304+ lastChunks . push ( inspect ( output ) ) ;
305+
129306 if ( expected . length ) {
130- assert . strictEqual ( output , expected [ 0 ] ) ;
307+ try {
308+ assert . strictEqual ( output , expected [ 0 ] ) ;
309+ } catch ( e ) {
310+ console . error ( `Failed test # ${ numtests - tests . length } ` ) ;
311+ console . error ( 'Last outputs: ' + inspect ( lastChunks , {
312+ breakLength : 5 , colors : true
313+ } ) ) ;
314+ throw e ;
315+ }
131316 expected . shift ( ) ;
132317 }
133318
134319 next ( ) ;
135320 }
136321 } ) ,
137- prompt : prompt ,
322+ completer : opts . completer ,
323+ prompt,
138324 useColors : false ,
139325 terminal : true
140326 } , function ( err , repl ) {
@@ -153,7 +339,13 @@ function runTest() {
153339 setImmediate ( runTestWrap , true ) ;
154340 } ) ;
155341
156- repl . inputStream . run ( test ) ;
342+ if ( opts . columns ) {
343+ Object . defineProperty ( repl , 'columns' , {
344+ value : opts . columns ,
345+ enumerable : true
346+ } ) ;
347+ }
348+ repl . inputStream . run ( opts . test ) ;
157349 } ) ;
158350}
159351
0 commit comments