11import * as fs from 'fs'
2- import * as path from ' path'
2+ import * as path from " path"
33import { sha256 } from 'js-sha256'
4- import TextWrap from '../../src/TextWrap'
4+ import TextWrap , { WrapStyle } from '../../src/TextWrap'
55
66/**
77 * @author [S. Mahdi Mir-Ismaili](https://mirismaili.github.io)
@@ -11,66 +11,175 @@ import TextWrap from '../../src/TextWrap'
1111const inputExpectedHash = '117677f3e12ded864c527d4f03583d4dd0be3cc0542c3cbbdbb01574dcf280c8'
1212const outputExpectedHash = '2e1bd0f9ae5b0ee9406908f58bd5b4030bbcdf464e5462e0fd1b142d49dbac2d'
1313
14- const input = fs . readFileSync ( path . resolve ( __dirname , 'stub.txt' ) , 'utf8' )
14+ const input = fs . readFileSync ( path . resolve ( __dirname , 'stub' , 'input .txt') , 'utf8' )
1515 . replace ( / \r \n | \r / g, '\n' )
1616
17- const indents = ''
18- const maxLineLength = 50
19- const textWrapper = new TextWrap ( { wrapOn : maxLineLength } )
20- const wrapResult = textWrapper . wrap ( input , indents )
21- const output = wrapResult . wrappedText
17+ const allOptions : ( Options | undefined ) [ ] = [
18+ undefined ,
19+ {
20+ indents : '\t' ,
21+ wrapOn : 50 ,
22+ continuationIndent : '' ,
23+ } ,
24+ {
25+ indents : '' ,
26+ wrapOn : 51 ,
27+ continuationIndent : '\t' ,
28+ } ,
29+ {
30+ indents : '' ,
31+ wrapOn : 52 ,
32+ continuationIndent : ' ' ,
33+ } ,
34+ ]
2235
23- const joinedInput = input . replace ( / \n / g, '' )
24- const joinedOutput = output . replace ( / \n / g, '' )
36+ const outputs : string [ ] = [ ]
2537
26- describe ( 'General tests:' , ( ) => {
27- it ( 'Check output itself' , ( ) => expect ( joinedOutput ) . toBe ( joinedInput ) )
38+ describe ( 'Case-specific tests:' , ( ) => {
39+ it ( "Check input's hash" , ( ) => expect ( sha256 ( input ) ) . toBe ( inputExpectedHash ) )
40+ } )
41+
42+ afterAll ( ( ) => {
43+ for ( let i = 0 ; i < allOptions . length ; ++ i )
44+ fs . writeFile ( outputPath ( i ) , outputs [ i ] , err => { if ( err ) console . error ( err ) } )
45+ } )
46+
47+ for ( let testNum = 0 ; testNum < allOptions . length ; ++ testNum ) {
48+ const options = allOptions [ testNum ]
49+ const textWrap = new TextWrap ( options )
2850
29- it ( 'Check num of markers' , ( ) => expect ( wrapResult . markers . length ) . toBe ( output . length - input . length ) )
51+ const maxLineLength = textWrap . wrapOn
52+ const continuationIndent = textWrap . continuationIndent
53+ const bc = textWrap . breakableCharactersClass
54+ const ec = textWrap . allowedExceedingCharactersClass
3055
31- it ( 'Try to find an illegal short line' ,
32- // Two markers which [the distance between the first marker and the first breakable character after the second
33- // marker] is less than or equal with [maxLineLength]
34- ( ) => {
35- let a = 0
36- for ( let b of wrapResult . markers ) {
37- const regexp = / [ ^ \S \xA0 ] / g
38- regexp . lastIndex = b
39-
40- const upBound = regexp . test ( input ) ? regexp . lastIndex - 1 : input . length
41- const distance = textWrapper . getVisualLength ( input . slice ( a , upBound ) , 0 )
42-
43- expect ( distance ) . toBeGreaterThan ( maxLineLength )
44-
45- a = b
46- }
47- }
48- )
56+ const indents = options === undefined ? '' : options . indents
57+ const indentsN = indents + continuationIndent
4958
50- it ( "Try to find an illegal long line in output" , // A line that should to be wrapped, but didn't
51- ( ) => {
52- const regExp = new RegExp ( `^(?=.*[^\\S\\xA0\\n](?![^\\S\\xA0\\n]|$)).{${ maxLineLength } ,}[\\S\\xA0]` ,
53- 'gm' ) // https://regex101.com/r/bdWnCx/2/
54-
55- expect ( regExp . test ( output ) ) . toBeFalsy ( )
56- } )
59+ const wrapResult = textWrap . wrap ( input , indents )
60+ const output = outputs [ testNum ] = wrapResult . wrappedText
61+ const markers = wrapResult . markers
5762
58- it ( 'Check markers against output' , ( ) => {
59- let anotherOutput = ''
60- let a = 0
61- for ( const b of wrapResult . markers ) {
62- anotherOutput += input . slice ( a , b ) + '\n' + indents
63- a = b
64- }
63+ describe ( `General tests [${ testNum } ]:` , ( ) => {
64+ it ( 'Check num of markers' , ( ) => expect ( output . length ) . toBe ( input . length + markers . length * ( '\n' + indentsN ) . length ) )
65+
66+ it ( 'Reproduce output using markers' , ( ) => {
67+ let anotherOutput = ''
68+ let a = 0
69+ for ( const b of markers ) {
70+ anotherOutput += input . slice ( a , b ) + '\n' + indentsN
71+ a = b
72+ }
73+
74+ if ( a > 0 ) anotherOutput += input . slice ( a )
75+ else anotherOutput = input
76+
77+ expect ( anotherOutput ) . toBe ( output )
78+ } )
79+
80+ it ( 'Try to find an illegal short line' ,
81+ // Two markers which [the distance between the first marker and the first breakable character after the second
82+ // marker] is less than or equal with [maxLineLength]
83+ ( ) => {
84+ let a = 0
85+ const regExp = new RegExp ( bc . source ,
86+ bc . flags . appendIfNot ( 'g' ) )
87+
88+ for ( let b of markers ) {
89+ regExp . lastIndex = b
90+
91+ const upBound = regExp . test ( input ) ? regExp . lastIndex : input . length
92+ const slice = indentsN + input . slice ( a , upBound ) // is not strict in first cycle of the loop that hasn't indentsN
93+ const distance = textWrap . getVisualLength ( slice )
94+
95+ expect ( distance , `[${ slice } ]\n${ regExp } ` ) . toBeGreaterThan ( maxLineLength )
96+
97+ a = b
98+ }
99+ } )
65100
66- if ( a > 0 ) anotherOutput += input . slice ( a )
67- else anotherOutput = input
101+ it ( "Try to find an illegal long line" , // A line that should to be wrapped, but hasn't
102+ ( ) => {
103+ // https://regex101.com/r/4DaiXE/1/
104+ const regexp = new RegExp ( `^([^\\n]*)(?:(?!\\n)${ bc . source } )[^\\n]*(?!${ ec . source } ).` ,
105+ bc . flags . appendIfNot ( 'g' ) . appendIfNot ( 'm' ) )
106+
107+ while ( true ) {
108+ const match = regexp . exec ( output )
109+ if ( match === null ) break
110+
111+ const vLen = textWrap . getVisualLength ( match [ 0 ] )
112+ const wrongCondition = vLen > maxLineLength
113+
114+ if ( wrongCondition &&
115+ // Check to sure the line is breakable:
116+ textWrap . getVisualLength ( match [ 1 ] ) > textWrap . getVisualLength ( indentsN )
117+ )
118+ expect ( vLen , `[${ match [ 0 ] } ]\n${ regexp } ` ) . toBeLessThanOrEqual ( maxLineLength )
119+ }
120+ } )
68121
69- expect ( anotherOutput ) . toBe ( output )
122+ it ( "Try to find an illegal long line using RegExp" , // Same as above but using RegExp. This one is not strict because can only calculate length, but not vLen (visual-length)
123+ ( ) => {
124+ expect ( output ) . not . toMatch1 (
125+ // https://regex101.com/r/OfQoDb/1
126+ new RegExp (
127+ `^(?=.{${ indentsN . length } ,}[^\\w\\xA0\\n](?![^\\S\\n]|$)).{${ maxLineLength } ,}\\S` ,
128+ 'm' ) ,
129+ `The text will be in ${ outputPath ( testNum ) } ` )
130+ } )
70131 } )
71- } )
132+
133+ describe ( `Case-specific tests [${ testNum } ]:` , ( ) => {
134+ it ( "Check output's hash" , ( ) => expect ( sha256 ( output ) ) . toBe ( outputExpectedHash ) )
135+ } )
136+ }
137+ //*************************************************************************************/
72138
73- describe ( 'Case-specific tests:' , ( ) => {
74- it ( "Check input's hash" , ( ) => expect ( sha256 ( input ) ) . toBe ( inputExpectedHash ) )
75- it ( "Check output's hash" , ( ) => expect ( sha256 ( output ) ) . toBe ( outputExpectedHash ) )
139+ expect . extend ( {
140+ toMatch1 ( text : string , regExp : RegExp , msg : string | ( ( ) => string ) ) {
141+ const match1 = regExp . exec ( text )
142+ const passed = match1 !== null
143+
144+ const message = ( toClause : ( ) => string ) =>
145+ ( ) => `Expected the text to ${ toClause ( ) } \n${ typeof msg === 'string' ? msg : msg ( ) } `
146+
147+ return passed ?
148+ {
149+ message : message ( ( ) => `doesn't match ${ regExp } \nFirst match:\n[${ match1 } ]` ) ,
150+ pass : true ,
151+ } :
152+ {
153+ message : message ( ( ) => `match ${ regExp } ` ) ,
154+ pass : false ,
155+ }
156+ } ,
76157} )
158+
159+ String . prototype . appendIfNot = function ( part : string ) : string {
160+ return this . indexOf ( part ) === - 1 ? this as string + part : this as string
161+ }
162+
163+ declare global {
164+ namespace jest {
165+ // noinspection JSUnusedGlobalSymbols
166+ interface Matchers < R > {
167+ toMatch1 ( regExp : RegExp , msg : string ) : R
168+ }
169+ }
170+
171+ // noinspection JSUnusedGlobalSymbols
172+ interface String {
173+ appendIfNot ( part : string ) : string ;
174+ }
175+ }
176+
177+ //*************************************************************************************/
178+
179+ interface Options extends WrapStyle {
180+ indents : string
181+ }
182+
183+ function outputPath ( i : number ) {
184+ return path . resolve ( __dirname , 'stub' , `output (${ i } ).txt` )
185+ }
0 commit comments