import { UndoManager } from "../src" ; import { types , clone , getSnapshot , flow , Instance } from "mobx-state-tree" ; import { configure } from "mobx" ; let undoManager = { } ; const setUndoManagerSameTree = ( targetStore ) => { undoManager = targetStore . history ; } ; const setUndoManagerDifferentTree = ( targetStore , includeHooks ? ) => { undoManager = UndoManager . create ( { } , { targetStore , includeHooks } ) ; } ; const canTimeTravel = ( store ) => { expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 1 ) ; store . inc ( ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 2 ) ; store . inc ( ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 3 ) ; undoManager . undo ( ) ; expect ( store . x ) . toBe ( 2 ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( true ) ; undoManager . undo ( ) ; expect ( store . x ) . toBe ( 1 ) ; expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( true ) ; undoManager . redo ( ) ; expect ( store . x ) . toBe ( 2 ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( true ) ; store . inc ( ) ; expect ( store . x ) . toBe ( 3 ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; } ; test ( "same tree - can time travel" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; canTimeTravel ( store ) ; } ) ; test ( "different tree - can time travel" , ( ) => { const HistoryDifferentTreeStoreModel = types . model ( { x : 1 } ) . actions ( ( self ) => { setUndoManagerDifferentTree ( self ) ; return { inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryDifferentTreeStoreModel . create ( ) ; canTimeTravel ( store ) ; } ) ; test ( "same tree - can time travel and persist state" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 1 ) ; store . inc ( ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 2 ) ; store . inc ( ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 3 ) ; undoManager . undo ( ) ; expect ( store . x ) . toBe ( 2 ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( true ) ; const store2 = clone ( store ) ; const undoManager2 = store2 . history ; undoManager2 . undo ( ) ; expect ( store2 . x ) . toBe ( 1 ) ; expect ( undoManager2 . canUndo ) . toBe ( false ) ; expect ( undoManager2 . canRedo ) . toBe ( true ) ; undoManager2 . redo ( ) ; expect ( store2 . x ) . toBe ( 2 ) ; expect ( undoManager2 . canUndo ) . toBe ( true ) ; expect ( undoManager2 . canRedo ) . toBe ( true ) ; store2 . inc ( ) ; expect ( store2 . x ) . toBe ( 3 ) ; expect ( undoManager2 . canUndo ) . toBe ( true ) ; expect ( undoManager2 . canRedo ) . toBe ( false ) ; } ) ; test ( "can time travel with Mutable object" , ( ) => { const MutableUnion = types . union ( types . string , types . boolean , types . number , types . map ( types . late ( ( ) => MutableUnion ) ) , types . array ( types . late ( ( ) => MutableUnion ) ) ) ; const MutableStoreModel = types . model ( { mutable : MutableUnion } ) . actions ( ( self ) => { setUndoManagerDifferentTree ( self ) ; return { setProp ( k , v ) { ; ( self . mutable as any ) . set ( k , v ) ; } } ; } ) ; const store = MutableStoreModel . create ( { mutable : { } } ) ; const mutable = store . mutable ; expect ( getSnapshot ( mutable ) ) . toEqual ( { } ) ; expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( undoManager . history . length ) . toBe ( 0 ) ; store . setProp ( "foo" , 1 ) ; expect ( getSnapshot ( mutable ) ) . toEqual ( { foo : 1 } ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( undoManager . history . length ) . toBe ( 1 ) ; store . setProp ( "foo" , { } ) ; expect ( getSnapshot ( mutable ) ) . toEqual ( { foo : { } } ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( undoManager . history . length ) . toBe ( 2 ) ; undoManager . undo ( ) ; expect ( getSnapshot ( mutable ) ) . toEqual ( { foo : 1 } ) ; expect ( undoManager . canUndo ) . toBe ( true ) ; expect ( undoManager . canRedo ) . toBe ( true ) ; expect ( undoManager . history . length ) . toBe ( 2 ) ; undoManager . undo ( ) ; expect ( getSnapshot ( mutable ) ) . toEqual ( { } ) ; expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( true ) ; expect ( undoManager . history . length ) . toBe ( 2 ) ; } ) ; const withoutUndo = ( store ) => { expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 1 ) ; undoManager . withoutUndo ( ( ) => store . inc ( ) ) ; expect ( undoManager . canUndo ) . toBe ( false ) ; expect ( undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 2 ) ; } ; test ( "same tree - withoutUndo" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; withoutUndo ( store ) ; } ) ; test ( "different tree - withoutUndo" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 } ) . actions ( ( self ) => { setUndoManagerDifferentTree ( self ) ; return { inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; withoutUndo ( store ) ; } ) ; test ( "same tree - withoutUndo declaratively" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc : ( ) => undoManager . withoutUndo ( ( ) => { self . x += 1 ; } ) } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; withoutUndo ( store ) ; } ) ; test ( "same tree - maxHistoryLength" , async ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryOnTreeStoreModel . create ( { } , { maxHistoryLength : 2 } ) ; expect ( undoManager . history . length ) . toBe ( 0 ) ; store . inc ( ) ; expect ( store . x ) . toBe ( 2 ) ; expect ( undoManager . history . length ) . toBe ( 1 ) ; store . inc ( ) ; expect ( store . x ) . toBe ( 3 ) ; expect ( undoManager . history . length ) . toBe ( 2 ) ; store . inc ( ) ; expect ( store . x ) . toBe ( 4 ) ; expect ( undoManager . history . length ) . toBe ( 2 ) ; store . inc ( ) ; expect ( store . x ) . toBe ( 5 ) ; expect ( undoManager . history . length ) . toBe ( 2 ) ; } ) ; test ( "same tree - withoutUndoFlow declaratively" , async ( ) => { let _undoManager = { } ; const _setUndoManagerSameTree = ( targetStore ) => { _undoManager = targetStore . history ; } ; const HistoryOnTreeStoreModel = types . model ( { x : 1 , y : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { _setUndoManagerSameTree ( self ) ; return { loadPosition2 : flow ( function * ( n ) { yield Promise . resolve ( ) ; self . y = n ; } ) , loadPosition : _undoManager . withoutUndoFlow ( function * ( ) { yield Promise . resolve ( ) ; self . x = 4 ; yield ( self as any ) . loadPosition2 ( 2 ) ; return { x : self . x , y : self . y } ; } ) } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; expect ( _undoManager . canUndo ) . toBe ( false ) ; expect ( _undoManager . canRedo ) . toBe ( false ) ; expect ( store . x ) . toBe ( 1 ) ; expect ( store . y ) . toBe ( 1 ) ; const value = await store . loadPosition ( ) ; expect ( store . x ) . toBe ( 4 ) ; expect ( store . y ) . toBe ( 2 ) ; expect ( value . x ) . toBe ( 4 ) ; expect ( value . y ) . toBe ( 2 ) ; expect ( _undoManager . canUndo ) . toBe ( false ) ; expect ( _undoManager . canRedo ) . toBe ( false ) ; } ) ; test ( "same tree - group" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , numbers : types . optional ( types . array ( types . number ) , [ ] ) , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc ( ) { self . x += 1 ; } , addNumber ( n ) { self . numbers . push ( n ) ; } } ; } ) ; const store = HistoryOnTreeStoreModel . create ( ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; expect ( store . x ) . toBe ( 1 ) ; undoManager . startGroup ( ( ) => { store . inc ( ) ; store . inc ( ) ; store . inc ( ) ; } ) ; expect ( store . x ) . toBe ( 4 ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; store . inc ( ) ; expect ( store . x ) . toBe ( 5 ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; undoManager . stopGroup ( ) ; expect ( store . x ) . toBe ( 5 ) ; expect ( undoManager . undoLevels ) . toBe ( 1 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; expect ( undoManager . history . length ) . toBe ( 1 ) ; expect ( undoManager . history [ 0 ] . patches ) . toEqual ( [ { op : "replace" , path : "/x" , value : 2 } , { op : "replace" , path : "/x" , value : 3 } , { op : "replace" , path : "/x" , value : 4 } , { op : "replace" , path : "/x" , value : 5 } ] ) ; undoManager . undo ( ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 1 ) ; expect ( store . x ) . toBe ( 1 ) ; undoManager . startGroup ( ( ) => { store . addNumber ( 1 ) ; store . addNumber ( 2 ) ; } ) ; expect ( store . numbers . length ) . toBe ( 2 ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 1 ) ; undoManager . stopGroup ( ) ; expect ( undoManager . undoLevels ) . toBe ( 1 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; undoManager . undo ( ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 1 ) ; expect ( store . numbers . length ) . toBe ( 0 ) ; } ) ; describe ( "same tree - clean" , ( ) => { const HistoryOnTreeStoreModel = types . model ( { x : 1 , history : types . optional ( UndoManager , { } ) } ) . actions ( ( self ) => { setUndoManagerSameTree ( self ) ; return { inc ( ) { self . x ++ ; } } ; } ) ; let store ; beforeEach ( ( ) => { store = HistoryOnTreeStoreModel . create ( ) ; store . inc ( ) ; store . inc ( ) ; store . history . undo ( ) ; expect ( undoManager . undoLevels ) . toBe ( 1 ) ; expect ( undoManager . redoLevels ) . toBe ( 1 ) ; } ) ; test ( "clear all" , ( ) => { store . history . clear ( ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; } ) ; test ( "clear redo" , ( ) => { store . history . clearRedo ( ) ; expect ( undoManager . undoLevels ) . toBe ( 1 ) ; expect ( undoManager . redoLevels ) . toBe ( 0 ) ; } ) ; test ( "clear undo" , ( ) => { store . history . clearUndo ( ) ; expect ( undoManager . undoLevels ) . toBe ( 0 ) ; expect ( undoManager . redoLevels ) . toBe ( 1 ) ; } ) ; } ) ; test ( "#1195 - withoutUndo() used inside an action should NOT affect all patches within that action" , ( ) => { configure ( { useProxies : "never" } ) ; let _undoManager ; const store = types . model ( `Person` , { name : types . string , age : types . number } ) . actions ( ( self ) => { _undoManager = UndoManager . create ( { } , { targetStore : self } ) ; return { setName ( name ) { _undoManager . withoutUndo ( ( ) => { self . name = name ; } ) ; } , setAge ( age ) { self . age = age ; } , setInfo ( name , age ) { this . setName ( name ) ; this . setAge ( age ) ; } } ; } ) ; const person = store . create ( { name : `Jon` , age : 20 } ) ; person . setInfo ( `Bob` , 50 ) ; expect ( person . name ) . toBe ( "Bob" ) ; expect ( person . age ) . toBe ( 50 ) ; expect ( _undoManager . history . length ) . toBe ( 1 ) ; expect ( _undoManager . history [ 0 ] ) . toEqual ( { inversePatches : [ { op : "replace" , path : "/age" , value : 20 } ] , patches : [ { op : "replace" , path : "/age" , value : 50 } ] } ) ; _undoManager . undo ( ) ; expect ( person . name ) . toBe ( "Bob" ) ; expect ( person . age ) . toBe ( 20 ) ; } ) ; describe ( "includeHooks flag" , ( ) => { test ( "does not record hooks by default" , ( ) => { const HistoryDifferentTreeStoreModel = types . model ( { x : 1 , y : 1 } ) . actions ( ( self ) => { setUndoManagerDifferentTree ( self ) ; return { afterCreate ( ) { self . y = 2 ; } , inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryDifferentTreeStoreModel . create ( ) ; store . inc ( ) ; expect ( undoManager . history ) . toHaveLength ( 1 ) ; expect ( undoManager . history [ 0 ] ) . toEqual ( { inversePatches : [ { op : "replace" , path : "/x" , value : 1 } ] , patches : [ { op : "replace" , path : "/x" , value : 2 } ] } ) ; } ) ; test ( "record hooks in the root store" , ( ) => { const HistoryDifferentTreeStoreModel = types . model ( { x : 1 , y : 1 } ) . actions ( ( self ) => { setUndoManagerDifferentTree ( self , true ) ; return { afterCreate ( ) { self . y = 2 ; } , inc ( ) { self . x += 1 ; } } ; } ) ; const store = HistoryDifferentTreeStoreModel . create ( ) ; store . inc ( ) ; expect ( undoManager . history ) . toHaveLength ( 2 ) ; expect ( undoManager . history ) . toEqual ( [ { inversePatches : [ { op : "replace" , path : "/y" , value : 1 } ] , patches : [ { op : "replace" , path : "/y" , value : 2 } ] } , { inversePatches : [ { op : "replace" , path : "/x" , value : 1 } ] , patches : [ { op : "replace" , path : "/x" , value : 2 } ] } ] ) ; } ) ; } ) ;