@@ -609,7 +609,7 @@ describe('TimeTravel', () => {
609609 const lastSnapshot = history [ history . length - 1 ] ;
610610 for ( const fiberSnapshot of lastSnapshot . fibers . values ( ) ) {
611611 if ( fiberSnapshot . displayName === 'MultiStateComponent' ) {
612- expect ( fiberSnapshot . hookStates . length ) . toBeGreaterThan ( 0 ) ;
612+ expect ( fiberSnapshot . hooks . length ) . toBeGreaterThan ( 0 ) ;
613613 }
614614 }
615615 } ) ;
@@ -652,5 +652,212 @@ describe('TimeTravel', () => {
652652 const history = timeTravel . getHistory ( ) ;
653653 expect ( history . length ) . toBeGreaterThan ( 0 ) ;
654654 } ) ;
655+
656+ it ( 'should identify hook types correctly' , async ( ) => {
657+ const timeTravel = createTimeTravel ( {
658+ trackComponents : [ 'MultiStateComponent' ] ,
659+ dangerouslyRunInProduction : true ,
660+ } ) ;
661+
662+ render ( < MultiStateComponent /> ) ;
663+
664+ await act ( async ( ) => {
665+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
666+ } ) ;
667+
668+ const history = timeTravel . getHistory ( ) ;
669+ expect ( history . length ) . toBeGreaterThan ( 0 ) ;
670+
671+ const lastSnapshot = history [ history . length - 1 ] ;
672+ for ( const fiberSnapshot of lastSnapshot . fibers . values ( ) ) {
673+ if ( fiberSnapshot . displayName === 'MultiStateComponent' ) {
674+ const stateHooks = fiberSnapshot . hooks . filter ( ( hook ) => hook . hookType === 'State' ) ;
675+ expect ( stateHooks . length ) . toBeGreaterThan ( 0 ) ;
676+ expect ( stateHooks . every ( ( hook ) => hook . isEditable ) ) . toBe ( true ) ;
677+ }
678+ }
679+ } ) ;
680+ } ) ;
681+
682+ describe ( 'external state support' , ( ) => {
683+ it ( 'should capture external state if provided' , async ( ) => {
684+ let externalCounter = 0 ;
685+ const captureExternalState = vi . fn ( ( ) => [
686+ { key : 'counter' , value : externalCounter } ,
687+ ] ) ;
688+
689+ const timeTravel = createTimeTravel ( {
690+ dangerouslyRunInProduction : true ,
691+ captureExternalState,
692+ } ) ;
693+
694+ render ( < Counter /> ) ;
695+
696+ await act ( async ( ) => {
697+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
698+ } ) ;
699+
700+ expect ( captureExternalState ) . toHaveBeenCalled ( ) ;
701+ } ) ;
702+
703+ it ( 'should restore external state if provided' , async ( ) => {
704+ let externalCounter = 0 ;
705+ const restoreExternalState = vi . fn ( ( entries ) => {
706+ const counterEntry = entries . find ( ( entry : { key : string } ) => entry . key === 'counter' ) ;
707+ if ( counterEntry ) {
708+ externalCounter = counterEntry . value ;
709+ }
710+ } ) ;
711+
712+ const timeTravel = createTimeTravel ( {
713+ dangerouslyRunInProduction : true ,
714+ captureExternalState : ( ) => [ { key : 'counter' , value : externalCounter } ] ,
715+ restoreExternalState,
716+ } ) ;
717+
718+ render ( < Counter /> ) ;
719+
720+ await act ( async ( ) => {
721+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
722+ } ) ;
723+
724+ externalCounter = 5 ;
725+
726+ const incrementButton = screen . getByTestId ( 'increment' ) ;
727+
728+ await act ( async ( ) => {
729+ fireEvent . click ( incrementButton ) ;
730+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
731+ } ) ;
732+
733+ if ( timeTravel . canGoBack ( ) ) {
734+ timeTravel . goBack ( ) ;
735+ expect ( restoreExternalState ) . toHaveBeenCalled ( ) ;
736+ }
737+ } ) ;
738+ } ) ;
739+
740+ describe ( 'onBeforeRestore callback' , ( ) => {
741+ it ( 'should call onBeforeRestore before restoring' , async ( ) => {
742+ const onBeforeRestore = vi . fn ( ( ) => true ) ;
743+ const timeTravel = createTimeTravel ( {
744+ onBeforeRestore,
745+ dangerouslyRunInProduction : true ,
746+ } ) ;
747+
748+ render ( < Counter /> ) ;
749+
750+ await act ( async ( ) => {
751+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
752+ } ) ;
753+
754+ const incrementButton = screen . getByTestId ( 'increment' ) ;
755+
756+ await act ( async ( ) => {
757+ fireEvent . click ( incrementButton ) ;
758+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
759+ } ) ;
760+
761+ if ( timeTravel . canGoBack ( ) ) {
762+ timeTravel . goBack ( ) ;
763+ expect ( onBeforeRestore ) . toHaveBeenCalled ( ) ;
764+ }
765+ } ) ;
766+
767+ it ( 'should prevent restore if onBeforeRestore returns false' , async ( ) => {
768+ const onBeforeRestore = vi . fn ( ( ) => false ) ;
769+ const onRestore = vi . fn ( ) ;
770+ const timeTravel = createTimeTravel ( {
771+ onBeforeRestore,
772+ onRestore,
773+ dangerouslyRunInProduction : true ,
774+ } ) ;
775+
776+ render ( < Counter /> ) ;
777+
778+ await act ( async ( ) => {
779+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
780+ } ) ;
781+
782+ const incrementButton = screen . getByTestId ( 'increment' ) ;
783+
784+ await act ( async ( ) => {
785+ fireEvent . click ( incrementButton ) ;
786+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
787+ } ) ;
788+
789+ const indexBeforeGoBack = timeTravel . getCurrentIndex ( ) ;
790+
791+ if ( timeTravel . canGoBack ( ) ) {
792+ timeTravel . goBack ( ) ;
793+ expect ( onBeforeRestore ) . toHaveBeenCalled ( ) ;
794+ expect ( onRestore ) . not . toHaveBeenCalled ( ) ;
795+ expect ( timeTravel . getCurrentIndex ( ) ) . toBe ( indexBeforeGoBack ) ;
796+ }
797+ } ) ;
798+ } ) ;
799+
800+ describe ( 'utility methods' , ( ) => {
801+ it ( 'should return tracked fiber IDs' , async ( ) => {
802+ const timeTravel = createTimeTravel ( {
803+ dangerouslyRunInProduction : true ,
804+ } ) ;
805+
806+ render ( < Counter /> ) ;
807+
808+ await act ( async ( ) => {
809+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
810+ } ) ;
811+
812+ const fiberIds = timeTravel . getTrackedFiberIds ( ) ;
813+ expect ( Array . isArray ( fiberIds ) ) . toBe ( true ) ;
814+ } ) ;
815+
816+ it ( 'should return tracked component names' , async ( ) => {
817+ const timeTravel = createTimeTravel ( {
818+ dangerouslyRunInProduction : true ,
819+ } ) ;
820+
821+ render ( < Counter /> ) ;
822+
823+ await act ( async ( ) => {
824+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
825+ } ) ;
826+
827+ const componentNames = timeTravel . getTrackedComponentNames ( ) ;
828+ expect ( Array . isArray ( componentNames ) ) . toBe ( true ) ;
829+ } ) ;
830+
831+ it ( 'should return editable state count' , async ( ) => {
832+ const timeTravel = createTimeTravel ( {
833+ dangerouslyRunInProduction : true ,
834+ } ) ;
835+
836+ render ( < Counter /> ) ;
837+
838+ await act ( async ( ) => {
839+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
840+ } ) ;
841+
842+ const fiberIds = timeTravel . getTrackedFiberIds ( ) ;
843+ if ( fiberIds . length > 0 ) {
844+ const count = timeTravel . getEditableStateCount ( fiberIds [ 0 ] ) ;
845+ expect ( typeof count ) . toBe ( 'number' ) ;
846+ }
847+ } ) ;
848+
849+ it ( 'should report restore in progress state' , async ( ) => {
850+ const timeTravel = createTimeTravel ( {
851+ dangerouslyRunInProduction : true ,
852+ } ) ;
853+
854+ render ( < Counter /> ) ;
855+
856+ await act ( async ( ) => {
857+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
858+ } ) ;
859+
860+ expect ( timeTravel . isRestoreInProgress ( ) ) . toBe ( false ) ;
861+ } ) ;
655862 } ) ;
656863} ) ;
0 commit comments