credits
This is pretty much a re-implementation of kata done by Mark Seeman in FSharp .
so all credits go to him. The goal is to learn property based testing from something that had already been implement in functional programming language and transfer that knowledge to javascript. So here we go.
kata
Kata was first described by Seb Rose in Recycling tests in TDD .
Given a letter, print a diamond starting with āAā with the supplied letter at the widest point. For example: print-diamond āCā prints
A
B B
C C
B B
A
code
Non empty diamond
test ( ' diamond is non-empty ' , t => {
t . true ( isNotEmpty ( diamond ( letter )))
})
const not = fn => (... val ) => ! fn (... val )
const isEmpty = obj => obj === ''
const isNotEmpty = not ( isEmpty )
const diamond = _ => ' _ '
First and last row contains A
test ( ' first row contains A ' , t => {
const d = diamond ( letter )
t . is ( compose ( trim , head , splitLF )( d ), ' A ' )
})
test ( ' last row contains A ' , t => {
const d = diamond ( letter )
t . is ( compose ( trim , last , splitLF )( d ), ' A ' )
})
const trim = s => s . trim ()
const split = c => s => s . split ( c )
const splitLF = split ( ' \n ' )
const diamond = _ => ' A '
All rows have symmetric contour
test ( ' all rows have symmetric contour ' , t => {
const d = diamond ( letter )
t . true (
compose (
all ( x => leadingSpaces ( x ) === trailingSpaces ( x )),
map ( withoutLF ),
splitLF
)( d )
)
})
const isLetter = c => / [ A-Z ] / . test ( c )
const leadingSpaces = s => s . substring ( 0 , firstIndex ( isLetter )( s ))
const trailingSpaces = s => s . substring ( lastIndex ( isLetter )( s ) + 1 )
const replace = ( x , y ) => s => s . replace ( new RegExp ( x , ' g ' ), y )
const withoutLF = replace ( ' \\ n ' , '' )
const diamond = _ => ' A '
All rows containt the correct letter in the correct order
test ( ' rows contain the correct letter in the correct order ' , t => {
const d = diamond ( letter )
const l = letters ( ' A ' , letter )
t . deepEqual (
compose ( map ( head ), map ( trim ), splitLF )( d ),
[... l , ... compose ( tail , reverse )( l )]
)
})
const diamond = l => {
const s = ' A '
const d = distance ( s , l )
return compose (
join ( ' \n ' ),
xs => [... xs , ... compose ( tail , reverse )( xs )],
map ( makeLine ( d ))
)( letters ( s , l ))
}
const toCharCode = c => c . charCodeAt ( 0 )
const fromCharCode = c => String . fromCharCode ( c )
const letters = ( s , e , l = []) =>
toCharCode ( s ) > toCharCode ( e )
? l
: letters ( fromCharCode ( toCharCode ( s ) + 1 ), e , [... l , s ])
const distance = ( s , e ) => toCharCode ( e ) - toCharCode ( s )
const join = c => xs => xs . join ( c )
const spaces = t => new Array ( t ). fill ( ' ' ). join ( '' )
const makeLine = d => s => spaces ( 3 ) + s + spaces ( 3 )
As wide as high
test ( ' as wide as high ' , t => {
const d = diamond ( letter )
const height = splitLF ( d ). length
t . true (
compose (
all ( x => x . length === height ),
map ( withoutLF ),
splitLF
)( d )
)
})
All lines except top and bottom have two identical letters
test ( ' all lines except top and bottom have two identical letters ' , t => {
const d = diamond ( letter )
t . true (
compose (
all ( isTwoIdenticalLetters ),
filter ( not ( includes ( ' A ' ))),
map ( withoutSpaces ),
splitLF
)( d )
)
})
const withoutSpaces = replace ( ' ' , '' )
const isTwoIdenticalLetters = s =>
s . length === 2 && s [ 0 ] === s [ 1 ]
Lower left space is a triangle
test ( ' lower left space is a triangle ' , t => {
const d = diamond ( letter )
const dist = distance ( ' A ' , letter )
t . deepEqual (
compose (
map ( x => x . length ),
map ( leadingSpaces ),
skipWhile (([ x ]) => isNotLetter ( x )),
splitLF
)( d ),
ints ( 0 , dist )
)
})
const ints = ( s , e , l = []) =>
s > e
? l
: ints ( s + 1 , e , [... l , s ])
const skipWhile = fn =>
compose (
snd ,
xs => xs . reduce (
([ s , l ], c ) => s && fn ( c )
? [ true , []]
: [ false , [... l , c ]],
[ true , []]
)
)
Figure is symmetric around horizontal axis
test ( ' figure is symmetric around horizontal axis ' , t => {
const d = diamond ( letter )
t . deepEqual (
compose (
map ( withoutLF ),
splitLF
)( d ),
compose (
reverse ,
map ( withoutLF ),
splitLF
)( d )
)
})
const makeLine = d => ( s , i ) => {
const padding = spaces ( d - i )
return s === ' A '
? padding + s + padding
: padding + s + spaces ( i * 2 - 1 ) + s + padding
The full source code can be found here .