@@ -754,6 +754,159 @@ describe('Onyx', () => {
754754 } ) ;
755755 } ) ;
756756
757+ describe ( 'getCollectionData' , ( ) => {
758+ it ( 'should return a frozen object' , async ( ) => {
759+ await initOnyx ( ) ;
760+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
761+
762+ const result = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
763+ expect ( result ) . toBeDefined ( ) ;
764+ expect ( Object . isFrozen ( result ) ) . toBe ( true ) ;
765+ } ) ;
766+
767+ it ( 'should return the same reference when nothing changed' , async ( ) => {
768+ await initOnyx ( ) ;
769+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
770+
771+ const first = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
772+ const second = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
773+ expect ( first ) . toBe ( second ) ;
774+ } ) ;
775+
776+ it ( 'should return a new reference after a member is updated' , async ( ) => {
777+ await initOnyx ( ) ;
778+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
779+
780+ const before = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
781+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 2 } ) ;
782+ const after = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
783+
784+ expect ( before ) . not . toBe ( after ) ;
785+ expect ( after ) . toEqual ( { [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] : { id : 2 } } ) ;
786+ } ) ;
787+
788+ it ( 'should return a new reference after a member is added' , async ( ) => {
789+ await initOnyx ( ) ;
790+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
791+
792+ const before = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
793+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 2` , { id : 2 } ) ;
794+ const after = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
795+
796+ expect ( before ) . not . toBe ( after ) ;
797+ expect ( after ) . toEqual ( {
798+ [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] : { id : 1 } ,
799+ [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 2` ] : { id : 2 } ,
800+ } ) ;
801+ } ) ;
802+
803+ it ( 'should return a stable empty reference for empty collections when keys are loaded' , async ( ) => {
804+ await initOnyx ( ) ;
805+ // Set a key so storageKeys is non-empty, but not a member of MOCK_COLLECTION
806+ await Onyx . set ( ONYX_KEYS . TEST_KEY , 'value' ) ;
807+
808+ const first = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
809+ const second = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
810+
811+ expect ( first ) . toBeDefined ( ) ;
812+ expect ( first ) . toBe ( second ) ;
813+ expect ( Object . keys ( first ! ) ) . toHaveLength ( 0 ) ;
814+ } ) ;
815+
816+ it ( 'should return undefined for empty collections when no keys are loaded' , async ( ) => {
817+ await initOnyx ( ) ;
818+
819+ const result = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
820+ expect ( result ) . toBeUndefined ( ) ;
821+ } ) ;
822+
823+ it ( 'should preserve unchanged member references when a sibling is updated' , async ( ) => {
824+ await initOnyx ( ) ;
825+ const member1Value = { id : 1 , name : 'unchanged' } ;
826+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , member1Value ) ;
827+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 2` , { id : 2 } ) ;
828+
829+ const before = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
830+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 2` , { id : 3 } ) ;
831+ const after = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
832+
833+ // Snapshot reference changed (sibling updated)
834+ expect ( before ) . not . toBe ( after ) ;
835+ // But unchanged member keeps the same reference
836+ expect ( after ?. [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] ) . toBe ( member1Value ) ;
837+ } ) ;
838+ } ) ;
839+
840+ describe ( 'hasValueChanged' , ( ) => {
841+ it ( 'should return false for the same reference (fast path)' , async ( ) => {
842+ await initOnyx ( ) ;
843+ const value = { id : 1 , name : 'test' } ;
844+ cache . set ( 'test' , value ) ;
845+
846+ expect ( cache . hasValueChanged ( 'test' , value ) ) . toBe ( false ) ;
847+ } ) ;
848+
849+ it ( 'should return false for deep-equal but different reference' , async ( ) => {
850+ await initOnyx ( ) ;
851+ cache . set ( 'test' , { id : 1 , name : 'test' } ) ;
852+
853+ expect ( cache . hasValueChanged ( 'test' , { id : 1 , name : 'test' } ) ) . toBe ( false ) ;
854+ } ) ;
855+
856+ it ( 'should return true when value differs' , async ( ) => {
857+ await initOnyx ( ) ;
858+ cache . set ( 'test' , { id : 1 } ) ;
859+
860+ expect ( cache . hasValueChanged ( 'test' , { id : 2 } ) ) . toBe ( true ) ;
861+ } ) ;
862+ } ) ;
863+
864+ describe ( 'merge' , ( ) => {
865+ it ( 'should not mark collection dirty when merged value is unchanged' , async ( ) => {
866+ await initOnyx ( ) ;
867+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 , name : 'test' } ) ;
868+
869+ const before = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
870+
871+ // Merge with identical values — fastMerge returns same reference, so no-op
872+ cache . merge ( { [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] : { id : 1 , name : 'test' } } ) ;
873+
874+ const after = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
875+ expect ( before ) . toBe ( after ) ;
876+ } ) ;
877+
878+ it ( 'should mark collection dirty when a member value changes' , async ( ) => {
879+ await initOnyx ( ) ;
880+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
881+
882+ const before = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
883+
884+ cache . merge ( { [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] : { id : 2 } } ) ;
885+
886+ const after = cache . getCollectionData ( ONYX_KEYS . COLLECTION . MOCK_COLLECTION ) ;
887+ expect ( before ) . not . toBe ( after ) ;
888+ expect ( after ! [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] ) . toEqual ( { id : 2 } ) ;
889+ } ) ;
890+
891+ it ( 'should handle null values by removing the key from storageMap' , async ( ) => {
892+ await initOnyx ( ) ;
893+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
894+
895+ cache . merge ( { [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] : null } ) ;
896+
897+ expect ( cache . get ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ) ) . toBeUndefined ( ) ;
898+ } ) ;
899+
900+ it ( 'should skip undefined values without modifying storageMap' , async ( ) => {
901+ await initOnyx ( ) ;
902+ await Onyx . set ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` , { id : 1 } ) ;
903+
904+ cache . merge ( { [ `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ] : undefined } ) ;
905+
906+ expect ( cache . get ( `${ ONYX_KEYS . COLLECTION . MOCK_COLLECTION } 1` ) ) . toEqual ( { id : 1 } ) ;
907+ } ) ;
908+ } ) ;
909+
757910 it ( 'should save RAM-only keys' , ( ) => {
758911 const testKeys = {
759912 ...ONYX_KEYS ,
0 commit comments