import { types , getSnapshot , unprotect , cast , detach , clone , SnapshotIn , getNodeId } from "../../src" ; describe ( "snapshotProcessor" , ( ) => { describe ( "over a model type" , ( ) => { const M = types . model ( { x : types . string } ) ; test ( "no processors" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { } ) } ) ; const model = Model . create ( { m : { x : "hi" } } ) ; unprotect ( model ) ; expect ( model . m . x ) . toBe ( "hi" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "hi" ) ; model . m = { x : "ho" } ; expect ( model . m . x ) . toBe ( "ho" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "ho" ) ; } ) ; test ( "pre processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return { ... sn , x : String ( sn . x ) } ; } } ) } ) ; const model = Model . create ( { m : { x : 5 } } ) ; unprotect ( model ) ; expect ( model . m . x ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "5" ) ; model . m = cast ( { x : 6 } ) ; expect ( model . m . x ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "6" ) ; } ) ; test ( "post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { postProcessor ( sn ) { return { ... sn , x : Number ( sn . x ) } ; } } ) } ) ; const model = Model . create ( { m : { x : "5" } } ) ; unprotect ( model ) ; expect ( model . m . x ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 5 ) ; model . m = cast ( { x : "6" } ) ; expect ( model . m . x ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 6 ) ; } ) ; test ( "pre and post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return { ... sn , x : String ( sn . x ) } ; } , postProcessor ( sn ) { return { ... sn , x : Number ( sn . x ) } ; } } ) } ) ; const model = Model . create ( { m : { x : 5 } } ) ; unprotect ( model ) ; expect ( model . m . x ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 5 ) ; model . m = cast ( { x : 6 } ) ; expect ( model . m . x ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 6 ) ; expect ( getSnapshot ( clone ( model . m ) ) . x ) . toBe ( 6 ) ; } ) ; } ) ; describe ( "over a literal type" , ( ) => { const M = types . string ; test ( "no processors" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { } ) } ) ; const model = Model . create ( { m : "hi" } ) ; unprotect ( model ) ; expect ( model . m ) . toBe ( "hi" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( "hi" ) ; model . m = "ho" ; expect ( model . m ) . toBe ( "ho" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( "ho" ) ; } ) ; test ( "pre processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return String ( sn ) ; } } ) } ) ; const model = Model . create ( { m : 5 } ) ; unprotect ( model ) ; expect ( model . m ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( "5" ) ; model . m = 6 as any ; expect ( model . m ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( "6" ) ; } ) ; test ( "post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { postProcessor ( sn ) { return Number ( sn ) ; } } ) } ) ; const model = Model . create ( { m : "5" } ) ; unprotect ( model ) ; expect ( model . m ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( 5 ) ; model . m = "6" ; expect ( model . m ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( 6 ) ; } ) ; test ( "pre and post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return String ( sn ) ; } , postProcessor ( sn ) { return Number ( sn ) ; } } ) } ) ; const model = Model . create ( { m : 5 } ) ; unprotect ( model ) ; expect ( model . m ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( 5 ) ; model . m = "6" ; expect ( model . m ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m ) . toBe ( 6 ) ; expect ( getSnapshot ( clone ( model ) ) . m ) . toBe ( 6 ) ; } ) ; } ) ; describe ( "over an array type" , ( ) => { const M = types . array ( types . string ) ; test ( "no processors" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { } ) } ) ; const model = Model . create ( { m : [ "hi" ] } ) ; unprotect ( model ) ; expect ( model . m [ 0 ] ) . toBe ( "hi" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( "hi" ) ; model . m = cast ( [ "ho" ] ) ; expect ( model . m [ 0 ] ) . toBe ( "ho" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( "ho" ) ; } ) ; test ( "pre processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return sn . map ( ( n ) => String ( n ) ) ; } } ) } ) ; const model = Model . create ( { m : [ 5 ] } ) ; unprotect ( model ) ; expect ( model . m [ 0 ] ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( "5" ) ; model . m = cast ( [ 6 ] ) ; expect ( model . m [ 0 ] ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( "6" ) ; } ) ; test ( "post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { postProcessor ( sn ) { return sn . map ( ( n ) => Number ( n ) ) ; } } ) } ) ; const model = Model . create ( { m : [ "5" ] } ) ; unprotect ( model ) ; expect ( model . m [ 0 ] ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( 5 ) ; model . m = cast ( [ "6" ] ) ; expect ( model . m [ 0 ] ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( 6 ) ; } ) ; test ( "pre and post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return sn . map ( ( n ) => String ( n ) ) ; } , postProcessor ( sn ) { return sn . map ( ( n ) => Number ( n ) ) ; } } ) } ) ; const model = Model . create ( { m : [ 5 ] } ) ; unprotect ( model ) ; expect ( model . m [ 0 ] ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( 5 ) ; model . m = cast ( [ 6 ] ) ; expect ( model . m [ 0 ] ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m [ 0 ] ) . toBe ( 6 ) ; expect ( getSnapshot ( clone ( model . m ) ) [ 0 ] ) . toBe ( 6 ) ; } ) ; } ) ; describe ( "over a map type" , ( ) => { const M = types . map ( types . string ) ; test ( "no processors" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { } ) } ) ; const model = Model . create ( { m : { x : "hi" } } ) ; unprotect ( model ) ; expect ( model . m . get ( "x" ) ) . toBe ( "hi" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "hi" ) ; model . m . set ( "x" , "ho" ) ; expect ( model . m . get ( "x" ) ) . toBe ( "ho" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "ho" ) ; } ) ; test ( "pre processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return { ... sn , x : String ( sn . x ) } ; } } ) } ) ; const model = Model . create ( { m : { x : 5 } } ) ; unprotect ( model ) ; expect ( model . m . get ( "x" ) ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "5" ) ; model . m = cast ( { x : 6 } ) ; expect ( model . m . get ( "x" ) ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( "6" ) ; } ) ; test ( "post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { postProcessor ( sn ) { return { ... sn , x : Number ( sn . x ) } ; } } ) } ) ; const model = Model . create ( { m : { x : "5" } } ) ; unprotect ( model ) ; expect ( model . m . get ( "x" ) ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 5 ) ; model . m = cast ( { x : "6" } ) ; expect ( model . m . get ( "x" ) ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 6 ) ; } ) ; test ( "pre and post processor" , ( ) => { const Model = types . model ( { m : types . snapshotProcessor ( M , { preProcessor ( sn ) { return { ... sn , x : String ( sn . x ) } ; } , postProcessor ( sn ) { return { ... sn , x : Number ( sn . x ) } ; } } ) } ) ; const model = Model . create ( { m : { x : 5 } } ) ; unprotect ( model ) ; expect ( model . m . get ( "x" ) ) . toBe ( "5" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 5 ) ; model . m = cast ( { x : 6 } ) ; expect ( model . m . get ( "x" ) ) . toBe ( "6" ) ; expect ( getSnapshot ( model ) . m . x ) . toBe ( 6 ) ; expect ( getSnapshot ( clone ( model . m ) ) . x ) . toBe ( 6 ) ; } ) ; } ) ; test ( "chained transforms" , ( ) => { const TL = types . snapshotProcessor ( types . string , { preProcessor ( sn ) { return sn . trimLeft ( ) ; } , postProcessor ( sn ) { return "_" + sn ; } } ) ; const TB = types . snapshotProcessor ( TL , { preProcessor ( sn ) { return sn . trimRight ( ) ; } , postProcessor ( sn ) { return sn + "_" ; } } ) ; const M = types . model ( { name : TB } ) ; const t = TB . create ( " hello " ) ; expect ( t ) . toBe ( "hello" ) ; const m = M . create ( { name : " hello " } ) ; expect ( m . name ) . toBe ( "hello" ) ; expect ( getSnapshot ( m ) . name ) . toBe ( "_hello_" ) ; } ) ; describe ( "moving nodes around with a pre-processor" , ( ) => { const Task = types . model ( "Task" , { x : types . number } ) ; const Store = types . model ( { a : types . array ( types . snapshotProcessor ( Task , { preProcessor ( sn ) { return { x : Number ( sn . x ) } ; } } , "PTask" ) ) , b : types . array ( Task ) } ) ; test ( "moving from a to b" , ( ) => { const s = Store . create ( { a : [ { x : "1" } ] } ) ; unprotect ( s ) ; const n = s . a [ 0 ] ; detach ( n ) ; expect ( s . a . length ) . toBe ( 0 ) ; expect ( getSnapshot ( n ) ) . toEqual ( { x : 1 } ) ; s . b . push ( n ) ; expect ( s . b . length ) . toBe ( 1 ) ; expect ( getSnapshot ( s . b ) ) . toEqual ( [ { x : 1 } ] ) ; } ) ; test ( "moving from b to a" , ( ) => { const s = Store . create ( { b : [ { x : 1 } ] } ) ; unprotect ( s ) ; const n = s . b [ 0 ] ; detach ( n ) ; expect ( s . b . length ) . toBe ( 0 ) ; expect ( getSnapshot ( n ) ) . toEqual ( { x : 1 } ) ; s . a . push ( n ) ; expect ( s . a . length ) . toBe ( 1 ) ; expect ( getSnapshot ( s . a ) ) . toEqual ( [ { x : 1 } ] ) ; } ) ; } ) ; describe ( "moving nodes around with a post-processor" , ( ) => { const Task = types . model ( { x : types . number } ) ; const Store = types . model ( { a : types . array ( types . snapshotProcessor ( Task , { postProcessor ( sn ) { return { x : String ( sn . x ) } ; } } ) ) , b : types . array ( Task ) } ) ; test ( "moving from a to b" , ( ) => { const s = Store . create ( { a : [ { x : 1 } ] } ) ; unprotect ( s ) ; const n = s . a [ 0 ] ; detach ( n ) ; expect ( s . a . length ) . toBe ( 0 ) ; expect ( getSnapshot ( n ) ) . toEqual ( { x : "1" } ) ; s . b . push ( n ) ; expect ( s . b . length ) . toBe ( 1 ) ; expect ( getSnapshot ( s . b ) ) . toEqual ( [ { x : "1" } ] ) ; } ) ; test ( "moving from b to a" , ( ) => { const s = Store . create ( { b : [ { x : 1 } ] } ) ; unprotect ( s ) ; const n = s . b [ 0 ] ; detach ( n ) ; expect ( s . b . length ) . toBe ( 0 ) ; expect ( getSnapshot ( n ) ) . toEqual ( { x : 1 } ) ; s . a . push ( n ) ; expect ( s . a . length ) . toBe ( 1 ) ; expect ( getSnapshot ( s . a ) ) . toEqual ( [ { x : "1" } ] ) ; } ) ; } ) ; describe ( "assigning instances works" , ( ) => { const Todo = types . model ( "Todo" , { id : types . identifier } ) ; const TodoWithProcessor = types . snapshotProcessor ( Todo , { preProcessor ( snapshot ) { return snapshot ; } } ) ; const Store = types . model ( "TodoStore" , { todos : types . map ( TodoWithProcessor ) , instance : types . optional ( TodoWithProcessor , { id : "new" } ) } ) . actions ( ( self ) => ( { addTodo ( todo ) { self . todos . put ( todo ) ; } , setInstance ( next ) { self . instance = next ; } } ) ) ; test ( "using instances in maps work" , ( ) => { const store = Store . create ( ) ; const todo = TodoWithProcessor . create ( { id : "map" } ) ; store . addTodo ( todo ) ; expect ( store . todos . size ) . toBe ( 1 ) ; expect ( getSnapshot ( store . todos ) ) . toEqual ( { map : { id : "map" } } ) ; } ) ; test ( "using instances as values works" , ( ) => { const store = Store . create ( ) ; const todo = TodoWithProcessor . create ( { id : "map" } ) ; store . setInstance ( todo ) ; expect ( store . instance ) . toBe ( todo ) ; } ) ; test ( "using the non processed type in place of the processed one works" , ( ) => { const store = Store . create ( ) ; const todo = Todo . create ( { id : "map" } ) ; store . setInstance ( todo ) ; expect ( store . instance ) . toBe ( todo ) ; } ) ; test ( "using the processed type in place of the non processed one works" , ( ) => { const store = types . model ( "Store" , { instance : Todo } ) . actions ( ( self ) => ( { setInstance ( next ) { self . instance = next ; } } ) ) . create ( { instance : { id : "new" } } ) ; const todo = TodoWithProcessor . create ( { id : "map" } ) ; store . setInstance ( todo ) ; expect ( store . instance ) . toBe ( todo ) ; } ) ; } ) ; test ( "cached initial snapshots are ok" , ( ) => { const M2 = types . snapshotProcessor ( types . model ( { x : types . number } ) , { preProcessor ( sn ) { return { ... sn , x : 0 } ; } } ) ; const M1 = types . model ( { m2 : M2 } ) ; const M = types . model ( { m1 : M1 } ) ; const m = M . create ( { m1 : { m2 : { x : 10 } } } ) ; expect ( getSnapshot ( m ) ) . toEqual ( { m1 : { m2 : { x : 0 } } } ) ; } ) ; test ( "works with IType.is" , ( ) => { const Model = types . model ( { x : types . number } ) ; const model = Model . create ( { x : 1 } ) ; expect ( Model . is ( model ) ) . toBe ( true ) ; expect ( Model . is ( { x : 1 } ) ) . toBe ( true ) ; const ProcessedModel = types . snapshotProcessor ( Model , { preProcessor ( sn ) { const copy = { ... sn , x : sn . y } ; delete copy . y ; return copy ; } , postProcessor ( sn ) { const copy = { ... sn , y : sn . x } ; delete copy . x ; return copy ; } } ) ; const processedModel = ProcessedModel . create ( { y : 1 } ) ; expect ( ProcessedModel . is ( processedModel ) ) . toBe ( true ) ; expect ( ProcessedModel . is ( { y : 1 } ) ) . toBe ( true ) ; expect ( ProcessedModel . is ( Model ) ) . toBe ( false ) ; } ) ; describe ( "1776 - reconciliation in an array" , ( ) => { test ( "model with transformed property is reconciled" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier , x : types . number } ) , { preProcessor ( sn ) { if ( "x" in sn ) { throw new Error ( "sn has already been preprocessed" ) ; } return { id : sn . id , x : sn . y } ; } } ) ; const Store = types . model ( { items : types . array ( SP ) } ) . actions ( ( self ) => ( { setItems ( items ) { self . items = cast ( items ) ; } } ) ) ; const store = Store . create ( { items : [ { id : "1" , y : 0 } ] } ) ; const oldNodeId = getNodeId ( store . items [ 0 ] ) ; store . setItems ( [ { id : "1" , y : 1 } ] ) ; expect ( getNodeId ( store . items [ 0 ] ) ) . toBe ( oldNodeId ) ; } ) ; test ( "model with transformed identifier attribute is reconciled" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier } ) , { preProcessor ( sn ) { return { id : sn . foo } ; } } ) ; const Store = types . model ( { items : types . array ( SP ) } ) . actions ( ( self ) => ( { setItems ( items ) { self . items = cast ( items ) ; } } ) ) ; const store = Store . create ( { items : [ { foo : "1" } ] } ) ; const oldNodeId = getNodeId ( store . items [ 0 ] ) ; store . setItems ( [ { foo : "1" } ] ) ; expect ( getNodeId ( store . items [ 0 ] ) ) . toBe ( oldNodeId ) ; } ) ; } ) ; describe ( "single node reconcilication" , ( ) => { test ( "model with transformed property is reconciled" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier , x : types . number } ) , { preProcessor ( sn ) { if ( "x" in sn ) { throw new Error ( "sn has already been preprocessed" ) ; } return { id : sn . id , x : sn . y } ; } } ) ; const Store = types . model ( { item : SP } ) . actions ( ( self ) => ( { setItem ( item ) { self . item = cast ( item ) ; } } ) ) ; const store = Store . create ( { item : { id : "1" , y : 0 } } ) ; const oldNodeId = getNodeId ( store . item ) ; store . setItem ( { id : "1" , y : 1 } ) ; expect ( getNodeId ( store . item ) ) . toBe ( oldNodeId ) ; expect ( store . item . x ) . toBe ( 1 ) ; } ) ; test ( "model with transformed identifier property is reconciled" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier } ) , { preProcessor ( sn ) { return { id : sn . foo } ; } } ) ; const Store = types . model ( { item : SP } ) . actions ( ( self ) => ( { setItem ( item ) { self . item = cast ( item ) ; } } ) ) ; const store = Store . create ( { item : { foo : "1" } } ) ; const oldNodeId = getNodeId ( store . item ) ; store . setItem ( { foo : "1" } ) ; expect ( getNodeId ( store . item ) ) . toBe ( oldNodeId ) ; expect ( store . item . id ) . toBe ( "1" ) ; } ) ; test ( "1791 - model wrapped with maybe is reconciled" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier , x : types . number } ) , { preProcessor ( sn ) { return { id : sn . id , x : sn . y } ; } } ) ; const Store = types . model ( { item : types . maybe ( SP ) } ) . actions ( ( self ) => ( { setItem ( item ) { self . item = cast ( item ) ; } } ) ) ; const store = Store . create ( { item : { id : "1" , y : 0 } } ) ; const oldNodeId = getNodeId ( store . item ! ) ; store . setItem ( { id : "1" , y : 1 } ) ; expect ( getNodeId ( store . item ! ) ) . toBe ( oldNodeId ) ; expect ( store . item ?. x ) . toBe ( 1 ) ; } ) ; test ( "model wrapped with optional is reconciled" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier , x : types . number } ) , { preProcessor ( sn ) { return { id : sn . id , x : sn . y } ; } } ) ; const Store = types . model ( { item : types . optional ( SP , { id : "1" , y : 0 } ) } ) . actions ( ( self ) => ( { setItem ( item ? ) { self . item = cast ( item ) ; } } ) ) ; const store = Store . create ( ) ; const oldNodeId = getNodeId ( store . item ! ) ; expect ( store . item ?. x ) . toBe ( 0 ) ; store . setItem ( { id : "1" , y : 1 } ) ; expect ( getNodeId ( store . item ! ) ) . toBe ( oldNodeId ) ; expect ( store . item ?. x ) . toBe ( 1 ) ; store . setItem ( undefined ) ; expect ( getNodeId ( store . item ! ) ) . toBe ( oldNodeId ) ; expect ( store . item ?. x ) . toBe ( 0 ) ; } ) ; } ) ; test ( "1777 - preProcessor wrapped in maybe accepts undefined" , ( ) => { const SP = types . snapshotProcessor ( types . model ( { id : types . identifier , x : types . number } ) , { preProcessor ( sn ) { return { id : sn . id , x : sn . y } ; } } ) ; const Store = types . model ( { item : types . maybe ( SP ) } ) . actions ( ( self ) => ( { setItem ( item ? ) { self . item = cast ( item ) ; } } ) ) ; const store = Store . create ( ) ; expect ( store . item ) . toBeUndefined ( ) ; store . setItem ( { id : "1" , y : 1 } ) ; expect ( store . item ?. x ) . toBe ( 1 ) ; store . setItem ( undefined ) ; expect ( store . item ) . toBeUndefined ( ) ; } ) ; } ) ;