import { types , recordPatches , onSnapshot , unprotect , applySnapshot , applyPatch , SnapshotOut } from "../../src" ; class Decimal { public number : number ; public fraction : number ; constructor ( value ) { const parts = value . split ( "." ) ; this . number = Number ( parts [ 0 ] ) ; this . fraction = Number ( parts [ 1 ] ) ; } toNumber ( ) { return this . number + Number ( "0." + this . fraction ) ; } toString ( ) { return `${ this . number }.${ this . fraction }` ; } } { const DecimalPrimitive = types . custom < string , Decimal > ( { name : "Decimal" , fromSnapshot ( value , env ) { if ( env && env . test ) env . test ( value ) ; return new Decimal ( value ) ; } , toSnapshot ( value ) { return value . toString ( ) ; } , isTargetType ( value ) { return value instanceof Decimal ; } , getValidationMessage ( value ) { if ( /^-?\d+\.\d+$/ . test ( value ) ) return "" ; return `'${ value }' doesn't look like a valid decimal number` ; } } ) ; const Wallet = types . model ( { balance : DecimalPrimitive , lastTransaction : types . maybeNull ( DecimalPrimitive ) } ) ; test ( "it should allow for custom primitive types" , ( ) => { const w1 = Wallet . create ( { balance : new Decimal ( "2.5" ) } ) ; expect ( w1 . balance . number ) . toBe ( 2 ) ; expect ( w1 . balance . fraction ) . toBe ( 5 ) ; const w2 = Wallet . create ( { balance : "3.5" } ) ; expect ( w2 . balance . number ) . toBe ( 3 ) ; expect ( w2 . balance . fraction ) . toBe ( 5 ) ; if ( process . env . NODE_ENV !== "production" ) expect ( ( ) => Wallet . create ( { balance : "two point one" } ) ) . toThrow ( "(Invalid value for type 'Decimal': 'two point one' doesn't look like a valid decimal number)" ) ; } ) ; test ( "reassignments will work" , ( ) => { const w1 = Wallet . create ( { balance : "2.5" } ) ; unprotect ( w1 ) ; const p = recordPatches ( w1 ) ; const snapshots = [ ] ; onSnapshot ( w1 , ( s ) => { snapshots . push ( s ) ; } ) ; const b1 = w1 . balance ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . balance = "2.5" as any ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; expect ( w1 . balance ) . toBe ( b1 ) ; w1 . balance = new Decimal ( "2.5" ) ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . balance = new Decimal ( "3.5" ) ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . balance = "4.5" as any ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . lastTransaction = b1 ; expect ( w1 . lastTransaction ) . toBe ( b1 ) ; w1 . lastTransaction = null ; expect ( w1 . lastTransaction ) . toBe ( null ) ; expect ( snapshots ) . toMatchSnapshot ( ) ; p . stop ( ) ; expect ( p . patches ) . toMatchSnapshot ( ) ; } ) ; test ( "passes environment to fromSnapshot" , ( ) => { const env = { test : jest . fn ( ) } ; Wallet . create ( { balance : "3.0" } , env ) ; expect ( env . test ) . toBeCalledWith ( "3.0" ) ; } ) ; } { test ( "complex representation" , ( ) => { } ) ; const DecimalTuple = types . custom < [ number , number ] , Decimal > ( { name : "DecimalTuple" , fromSnapshot ( value ) { return new Decimal ( value [ 0 ] + "." + value [ 1 ] ) ; } , toSnapshot ( value ) { return [ value . number , value . fraction ] ; } , isTargetType ( value ) { return value instanceof Decimal ; } , getValidationMessage ( value ) { if ( Array . isArray ( value ) && value . length === 2 ) return "" ; return `'${ JSON . stringify ( value ) }' doesn't look like a valid decimal number` ; } } ) ; const Wallet = types . model ( { balance : DecimalTuple } ) ; test ( "it should allow for complex custom primitive types" , ( ) => { const w1 = Wallet . create ( { balance : new Decimal ( "2.5" ) } ) ; expect ( w1 . balance . number ) . toBe ( 2 ) ; expect ( w1 . balance . fraction ) . toBe ( 5 ) ; const w2 = Wallet . create ( { balance : [ 3 , 5 ] } ) ; expect ( w2 . balance . number ) . toBe ( 3 ) ; expect ( w2 . balance . fraction ) . toBe ( 5 ) ; if ( process . env . NODE_ENV !== "production" ) expect ( ( ) => Wallet . create ( { balance : "two point one" } as any ) ) . toThrow ( "(Invalid value for type 'DecimalTuple': '\"two point one\"' doesn't look like a valid decimal number)" ) ; } ) ; test ( "complex reassignments will work" , ( ) => { const w1 = Wallet . create ( { balance : [ 2 , 5 ] } ) ; unprotect ( w1 ) ; const p = recordPatches ( w1 ) ; const snapshots = [ ] ; onSnapshot ( w1 , ( s ) => { snapshots . push ( s ) ; } ) ; const b1 = w1 . balance ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . balance = [ 2 , 5 ] as any ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; expect ( w1 . balance ) . not . toBe ( b1 ) ; w1 . balance = new Decimal ( "2.5" ) ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . balance = new Decimal ( "3.5" ) ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; w1 . balance = [ 4 , 5 ] as any ; expect ( b1 ) . toBeInstanceOf ( Decimal ) ; expect ( snapshots ) . toMatchSnapshot ( ) ; p . stop ( ) ; expect ( p . patches ) . toMatchSnapshot ( ) ; } ) ; test ( "can apply snapshot and patch" , ( ) => { const w1 = Wallet . create ( { balance : [ 3 , 0 ] } ) ; applySnapshot ( w1 , { balance : [ 4 , 5 ] } ) ; expect ( w1 . balance ) . toBeInstanceOf ( Decimal ) ; expect ( w1 . balance . toString ( ) ) . toBe ( "4.5" ) ; applyPatch ( w1 , { op : "replace" , path : "/balance" , value : [ 5 , 0 ] } ) ; expect ( w1 . balance . toString ( ) ) . toBe ( "5.0" ) ; } ) ; }