import { Option , None , Some } from '../lib' ; describe ( 'option' , ( ) => { test ( 'isDefined can help refine the type' , ( ) => { const value = 33 as number | undefined ; const some = Option ( value ) ; some . isDefined ( ) && some . get ( ) . toFixed ( 10 ) ; } ) ; test ( 'Creating a Some' , ( ) => { const some = Option ( 10 ) ; expect ( some . isDefined ( ) ) . toBe ( true ) ; expect ( some . get ( ) ) . toBe ( 10 ) ; } ) ; test ( 'Creating a Some with an empty string' , ( ) => { const some = Option ( '' ) ; expect ( some . isDefined ( ) ) . toBe ( true ) ; expect ( some . get ( ) ) . toBe ( '' ) ; } ) ; test ( 'Creating a None with null' , ( ) => { const none = Option ( null ) ; expect ( ! none . isDefined ( ) ) . toBe ( true ) ; expect ( none . get ( ) ) . toBe ( undefined ) ; } ) ; test ( 'Creating a None with undefined' , ( ) => { const none = Option ( undefined ) ; expect ( none . isDefined ( ) ) . toBe ( false ) ; expect ( none . get ( ) ) . toBe ( undefined ) ; } ) ; test ( 'Some and None have a hidden back reference to Option for use in other libs without an explicit dep' , ( ) => { const some = Option ( 1 ) ; const some2 = ( some as any ) . Option ( 10 ) ; const none = Option ( null ) ; const none2 = ( none as any ) . Option ( undefined ) ; expect ( some2 . get ( ) ) . toBe ( 10 ) ; expect ( none2 ) . toBe ( None ) ; } ) ; test ( 'Manipulating a None' , ( ) => { const none = None ; const anOption = none ; const x = none . get ( ) ; const y = anOption . get ( ) ; x ; y ; } ) ; test ( 'Some.forEach' , ( ) => { let state = 0 ; Some ( 10 ) . forEach ( x => ( state = state + x ) ) ; Option ( 10 ) . forEach ( x => ( state = state + x ) ) ; expect ( state ) . toBe ( 20 ) ; } ) ; test ( 'None.forEach' , ( ) => { let state = 0 ; Option ( null ) . forEach ( x => ( state = 10 ) ) ; expect ( state ) . toBe ( 0 ) ; } ) ; test ( 'Some.map -> Some' , ( ) => { const some = Option ( 10 ) . map ( x => x * 2 ) ; expect ( some . isDefined ( ) && some . get ( ) === 20 ) . toBe ( true ) ; } ) ; test ( 'Some.map -> None' , ( ) => { const none = Option ( 10 ) . map ( x => undefined ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'None.map -> None' , ( ) => { const value = undefined as number | undefined ; const none = Option ( value ) . map ( x => x * 2 ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'Some.flatMap -> Some' , ( ) => { const some = Option ( 10 ) . flatMap ( x => Option ( x * 2 ) ) ; expect ( some . isDefined ( ) && some . get ( ) === 20 ) . toBe ( true ) ; } ) ; test ( 'Some.flatMap -> None' , ( ) => { const none = Option ( 10 ) . flatMap ( _ => None ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'None.flatMap -> Some' , ( ) => { const none = Option ( undefined ) . flatMap ( _ => Option ( 10 ) ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'None.flatMap -> None' , ( ) => { const none = Option ( undefined ) . flatMap ( _ => None ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'Some.filter -> Some' , ( ) => { const some = Option ( 10 ) . filter ( x => x > 5 ) ; expect ( some . isDefined ( ) && some . get ( ) === 10 ) . toBe ( true ) ; } ) ; test ( 'Some.filter -> None' , ( ) => { const none = Option ( 10 ) . filter ( x => x > 10 ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'None.filter -> true' , ( ) => { const none = Option ( undefined ) . filter ( _ => true ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'None.filter -> false' , ( ) => { const none = Option ( undefined ) . filter ( _ => false ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'filter can refine the Option type' , ( ) => { type Data = { type : 'a' ; data : string ; } | { type : 'b' ; data : number ; } ; const some = Option < Data > ( { type : 'a' , data : 'hey' } ) ; function isStringData ( data ) { return 'a' in data ; } some . filter ( isStringData ) . map ( str => str . data . charCodeAt ( 0 ) ) ; } ) ; test ( 'Some getOrElse' , ( ) => { const result = Option ( '' ) . getOrElse ( 'alt' ) ; expect ( result ) . toBe ( '' ) ; } ) ; test ( 'None getOrElse' , ( ) => { const value = undefined as number | undefined ; const result = Option ( value ) . getOrElse ( 20 ) ; expect ( result ) . toBe ( 20 ) ; } ) ; test ( 'Some.orElse Some' , ( ) => { const some = Option ( 10 ) . orElse ( ( ) => Option ( 20 ) ) ; expect ( some . isDefined ( ) && some . get ( ) === 10 ) . toBe ( true ) ; } ) ; test ( 'Some.orElse None' , ( ) => { const some = Option ( 10 ) . orElse ( ( ) => None ) ; expect ( some . isDefined ( ) && some . get ( ) === 10 ) . toBe ( true ) ; } ) ; test ( 'None.orElse Some' , ( ) => { const value = undefined as number | undefined ; const some = Option ( value ) . orElse ( ( ) => Option ( 20 ) ) ; expect ( some . isDefined ( ) && some . get ( ) === 20 ) . toBe ( true ) ; } ) ; test ( 'None.orElse None' , ( ) => { const none = Option ( undefined ) . orElse ( ( ) => None ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'Some.fold' , ( ) => { const some = Option ( 10 ) ; const result = some . fold ( ( ) => 999 , x => ( x * 2 ) . toString ( ) ) ; if ( typeof result === 'number' ) { result . toFixed ( ) ; } else { result . charCodeAt ( 0 ) ; } expect ( result ) . toBe ( '20' ) ; } ) ; test ( 'None.fold' , ( ) => { const none = Option ( null as number | null ) ; const result = none . fold ( ( ) => '999' , x => ( x * 2 ) . toString ( ) ) ; expect ( result ) . toBe ( '999' ) ; } ) ; test ( 'Some.contains' , ( ) => { expect ( Some ( '🍔' ) . contains ( '🍔' ) ) . toBe ( true ) ; expect ( Some ( '🍔' ) . contains ( '🥗' ) ) . toBe ( false ) ; } ) ; test ( 'None.contains' , ( ) => { const none = None as Option < '🍔' > ; expect ( none . contains ( '🍔' ) ) . toBe ( false ) ; } ) ; test ( 'Some.exists' , ( ) => { expect ( Some ( 10 ) . exists ( n => n == 10 ) ) . toBe ( true ) ; expect ( Some ( [ ] ) . exists ( arr => arr . length > 0 ) ) . toBe ( false ) ; } ) ; test ( 'exists can refine the Option type' , ( ) => { type Data = { type : 'a' ; data : string ; } | { type : 'b' ; data : number ; } ; const some = Option < Data > ( { type : 'a' , data : 'hey' } ) ; function isStringData ( data ) { return 'a' in data ; } if ( some . exists ( isStringData ) ) { some . map ( str => str . data . charCodeAt ( 0 ) ) ; } } ) ; test ( 'None.exists' , ( ) => { expect ( Option ( null ) . exists ( _ => true ) ) . toBe ( false ) ; } ) ; test ( 'Some.toArray' , ( ) => { const result = Option ( 10 ) . toArray ( ) ; expect ( result ) . toEqual ( [ 10 ] ) ; } ) ; test ( 'None.toArray' , ( ) => { const result = Option ( null ) . toArray ( ) ; expect ( result ) . toEqual ( [ ] ) ; } ) ; test ( 'Some is an iterable' , ( ) => { const result = Option ( 10 ) ; const acc = [ ] ; for ( let i of result ) { acc . push ( i ) ; } expect ( acc ) . toEqual ( [ 10 ] ) ; } ) ; test ( 'None is an iterable' , ( ) => { const result = Option ( null ) ; const acc = [ ] ; for ( let i of result ) { acc . push ( i ) ; } expect ( acc ) . toEqual ( [ ] ) ; } ) ; test ( 'Some.toResult' , ( ) => { const result = Option ( 10 ) . toResult ( ( ) => 'nope' ) ; expect ( result . isOk ( ) && result . get ( ) ) . toBe ( 10 ) ; } ) ; test ( 'None.toResult' , ( ) => { const result = Option ( null ) . toResult ( ( ) => 'error' ) ; expect ( ! result . isOk ( ) && result . get ( ) ) . toBe ( 'error' ) ; } ) ; test ( 'Option.all - 2 Some' , ( ) => { const some = Option . all ( [ Option ( 'a' ) , Option ( 'b' ) ] ) ; expect ( some . isDefined ( ) && some . get ( ) . join ( ',' ) === 'a,b' ) . toBe ( true ) ; } ) ; test ( 'Option.all - 1 Some, 1 defined value' , ( ) => { const some = Option . all ( [ Option ( 'a' ) , 'b' ] ) ; expect ( some . isDefined ( ) && some . get ( ) . join ( ',' ) === 'a,b' ) . toBe ( true ) ; } ) ; test ( 'Option.all - 3 Some' , ( ) => { const some = Option . all ( [ Option ( 'a' ) , Option ( 'b' ) , Option ( 'c' ) ] ) ; expect ( some . isDefined ( ) && some . get ( ) . join ( ',' ) === 'a,b,c' ) . toBe ( true ) ; } ) ; test ( 'Option.all - 1 Some, 1 None' , ( ) => { const none = Option . all ( [ Option ( 'a' ) , Option ( undefined ) ] ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'Option.all - 1 Some, 1 undefined' , ( ) => { const none = Option . all ( [ Option ( 'a' ) , undefined ] ) ; expect ( ! none . isDefined ( ) && none . get ( ) === undefined ) . toBe ( true ) ; } ) ; test ( 'Option.all - values in the result tuple are refined to be non nullable' , ( ) => { const nullableString = 'a' as string | null | undefined ; Option . all ( [ Option ( nullableString ) , undefined , nullableString ] ) . map ( ( [ a , b , c ] ) => { a . charCodeAt ( 10 ) ; c . charCodeAt ( 10 ) ; return 0 ; } ) ; } ) ; test ( 'Option.all - 10 Some' , ( ) => { const result = Option . all ( [ Some ( 1 ) , Some ( '2' ) , Some ( 3 ) , Some ( '4' ) , Some ( 5 ) , Some ( true ) , Some ( 7 ) , Some ( 8 ) , Some ( 9 ) , Some ( 10 ) ] ) ; const result2 = Option . all ( [ Some ( 1 ) , Some ( '2' ) , Some ( 3 ) , Some ( '4' ) , Some ( 5 ) , Some ( true ) ] ) ; expect ( result . isDefined ( ) && result . get ( ) [ 2 ] + result . get ( ) [ 9 ] === 13 ) . toBe ( true ) ; expect ( result . get ( ) ) . toEqual ( [ 1 , '2' , 3 , '4' , 5 , true , 7 , 8 , 9 , 10 ] ) ; expect ( result2 . isDefined ( ) && result2 . get ( ) [ 0 ] + result2 . get ( ) [ 4 ] === 6 ) . toBe ( true ) ; } ) ; test ( 'Option.isOption' , ( ) => { expect ( Option . isOption ( true ) ) . toBe ( false ) ; expect ( Option . isOption ( false ) ) . toBe ( false ) ; expect ( Option . isOption ( '' ) ) . toBe ( false ) ; expect ( Option . isOption ( [ ] ) ) . toBe ( false ) ; expect ( Option . isOption ( None ) ) . toBe ( true ) ; expect ( Option . isOption ( Option ( 33 ) ) ) . toBe ( true ) ; expect ( Option . isOption ( Option ( undefined ) ) ) . toBe ( true ) ; } ) ; test ( 'Some toString' , ( ) => { const str = Option ( 10 ) . toString ( ) ; expect ( str ) . toBe ( 'Some(10)' ) ; const obj = { a : 1 , toString ( ) { return `{a:${ this . a }}` ; } } ; const str2 = Option ( obj ) . toString ( ) ; expect ( str2 ) . toBe ( 'Some({a:1})' ) ; } ) ; test ( 'None toString' , ( ) => { const str = Option ( undefined ) . toString ( ) ; expect ( str ) . toBe ( 'None' ) ; } ) ; test ( 'Some toJSON' , ( ) => { const obj = JSON . parse ( JSON . stringify ( { x : Option ( 10 ) } ) ) ; expect ( obj . x ) . toBe ( 10 ) ; } ) ; test ( 'Some toJSON with implicit json for contained object' , ( ) => { const obj = JSON . parse ( JSON . stringify ( { x : Option ( { toJSON ( ) { return 10 ; } } ) } ) ) ; expect ( obj . x ) . toBe ( 10 ) ; } ) ; test ( 'None toJSON' , ( ) => { const obj = JSON . parse ( JSON . stringify ( { x : Option ( undefined ) } ) ) ; expect ( obj . x ) . toBe ( null ) ; } ) ; test ( 'Measure the time needed to create a Some' , ( ) => { } ) ; } ) ;