@@ -3112,3 +3112,299 @@ describe('Onyx.init', () => {
31123112 } ) ;
31133113 } ) ;
31143114} ) ;
3115+
3116+ // Separate describe block to control Onyx.init() per-test so we can pre-seed storage before init.
3117+ describe ( 'RAM-only keys should not read from storage' , ( ) => {
3118+ let cache : typeof OnyxCache ;
3119+
3120+ beforeEach ( ( ) => {
3121+ // Resets the deferred init task before each test.
3122+ Object . assign ( OnyxUtils . getDeferredInitTask ( ) , createDeferredTask ( ) ) ;
3123+ cache = require ( '../../lib/OnyxCache' ) . default ;
3124+ } ) ;
3125+
3126+ afterEach ( ( ) => {
3127+ jest . restoreAllMocks ( ) ;
3128+ return Onyx . clear ( ) ;
3129+ } ) ;
3130+
3131+ it ( 'should not return stale storage data for a RAM-only key via get' , async ( ) => {
3132+ // Simulate stale data left in storage from before the key was RAM-only
3133+ await StorageMock . setItem ( ONYX_KEYS . RAM_ONLY_TEST_KEY , 'stale_value' ) ;
3134+
3135+ Onyx . init ( {
3136+ keys : ONYX_KEYS ,
3137+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3138+ } ) ;
3139+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3140+
3141+ let receivedValue : unknown ;
3142+ const connection = Onyx . connect ( {
3143+ key : ONYX_KEYS . RAM_ONLY_TEST_KEY ,
3144+ callback : ( value ) => {
3145+ receivedValue = value ;
3146+ } ,
3147+ } ) ;
3148+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3149+
3150+ expect ( receivedValue ) . toBeUndefined ( ) ;
3151+ expect ( cache . get ( ONYX_KEYS . RAM_ONLY_TEST_KEY ) ) . toBeUndefined ( ) ;
3152+
3153+ Onyx . disconnect ( connection ) ;
3154+ } ) ;
3155+
3156+ it ( 'should not return stale storage data for RAM-only collection members via multiGet' , async ( ) => {
3157+ const collectionMember1 = `${ ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION } 1` ;
3158+ const collectionMember2 = `${ ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION } 2` ;
3159+
3160+ // Simulate stale collection members in storage
3161+ await StorageMock . setItem ( collectionMember1 , { name : 'stale_1' } ) ;
3162+ await StorageMock . setItem ( collectionMember2 , { name : 'stale_2' } ) ;
3163+
3164+ Onyx . init ( {
3165+ keys : ONYX_KEYS ,
3166+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3167+ } ) ;
3168+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3169+
3170+ let receivedCollection : OnyxCollection < unknown > ;
3171+ const connection = Onyx . connect ( {
3172+ key : ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION ,
3173+ callback : ( value ) => {
3174+ receivedCollection = value ;
3175+ } ,
3176+ waitForCollectionCallback : true ,
3177+ } ) ;
3178+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3179+
3180+ expect ( receivedCollection ) . toBeUndefined ( ) ;
3181+ expect ( cache . get ( collectionMember1 ) ) . toBeUndefined ( ) ;
3182+ expect ( cache . get ( collectionMember2 ) ) . toBeUndefined ( ) ;
3183+
3184+ Onyx . disconnect ( connection ) ;
3185+ } ) ;
3186+
3187+ it ( 'should not include stale RAM-only keys in getAllKeys results' , async ( ) => {
3188+ // Simulate stale data in storage
3189+ await StorageMock . setItem ( ONYX_KEYS . RAM_ONLY_TEST_KEY , 'stale_value' ) ;
3190+ await StorageMock . setItem ( `${ ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION } 1` , { stale : 'member' } ) ;
3191+ await StorageMock . setItem ( ONYX_KEYS . OTHER_TEST , 'normal_value' ) ;
3192+
3193+ Onyx . init ( {
3194+ keys : ONYX_KEYS ,
3195+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3196+ } ) ;
3197+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3198+
3199+ const keys = await OnyxUtils . getAllKeys ( ) ;
3200+
3201+ expect ( keys . has ( ONYX_KEYS . RAM_ONLY_TEST_KEY ) ) . toBe ( false ) ;
3202+ expect ( keys . has ( `${ ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION } 1` ) ) . toBe ( false ) ;
3203+ // Normal keys should still be present
3204+ expect ( keys . has ( ONYX_KEYS . OTHER_TEST ) ) . toBe ( true ) ;
3205+ } ) ;
3206+
3207+ it ( 'should not read stale storage data for RAM-only keys during initializeWithDefaultKeyStates' , async ( ) => {
3208+ // Simulate stale data for a RAM-only key that also has a default key state
3209+ await StorageMock . setItem ( ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE , 'stale_value' ) ;
3210+
3211+ Onyx . init ( {
3212+ keys : ONYX_KEYS ,
3213+ initialKeyStates : {
3214+ [ ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] : 'default_value' ,
3215+ } ,
3216+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3217+ } ) ;
3218+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3219+
3220+ // The cache should have the default value, not the stale storage value
3221+ expect ( cache . get ( ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ) ) . toEqual ( 'default_value' ) ;
3222+ } ) ;
3223+
3224+ it ( 'should not use stale storage data as merge base for RAM-only keys' , async ( ) => {
3225+ // Simulate stale data in storage
3226+ await StorageMock . setItem ( ONYX_KEYS . RAM_ONLY_TEST_KEY , { name : 'stale' , token : 'old_token' } ) ;
3227+
3228+ Onyx . init ( {
3229+ keys : ONYX_KEYS ,
3230+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3231+ } ) ;
3232+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3233+
3234+ // Merge new data — should NOT merge with stale storage value
3235+ await Onyx . merge ( ONYX_KEYS . RAM_ONLY_TEST_KEY , { name : 'new' } ) ;
3236+
3237+ // The result should only contain the merged value, not the stale token
3238+ expect ( cache . get ( ONYX_KEYS . RAM_ONLY_TEST_KEY ) ) . toEqual ( { name : 'new' } ) ;
3239+ } ) ;
3240+
3241+ it ( 'should not read stale storage data when subscribing to individual RAM-only collection members' , async ( ) => {
3242+ const collectionMember = `${ ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION } 1` ;
3243+
3244+ // Simulate stale data in storage
3245+ await StorageMock . setItem ( collectionMember , { data : 'stale' } ) ;
3246+
3247+ Onyx . init ( {
3248+ keys : ONYX_KEYS ,
3249+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3250+ } ) ;
3251+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3252+
3253+ const receivedValues : unknown [ ] = [ ] ;
3254+ const connection = Onyx . connect ( {
3255+ key : collectionMember ,
3256+ callback : ( value ) => {
3257+ receivedValues . push ( value ) ;
3258+ } ,
3259+ } ) ;
3260+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3261+
3262+ // Should never receive the stale value
3263+ expect ( receivedValues . every ( ( v ) => v === undefined || v === null ) ) . toBe ( true ) ;
3264+
3265+ Onyx . disconnect ( connection ) ;
3266+ } ) ;
3267+
3268+ it ( 'should still work correctly for normal keys when RAM-only keys have stale storage data' , async ( ) => {
3269+ // Simulate both normal and RAM-only stale data in storage
3270+ await StorageMock . setItem ( ONYX_KEYS . TEST_KEY , 'normal_value' ) ;
3271+ await StorageMock . setItem ( ONYX_KEYS . RAM_ONLY_TEST_KEY , 'stale_ram_value' ) ;
3272+
3273+ Onyx . init ( {
3274+ keys : ONYX_KEYS ,
3275+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3276+ } ) ;
3277+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3278+
3279+ let normalValue : unknown ;
3280+ let ramOnlyValue : unknown ;
3281+
3282+ const connection1 = Onyx . connect ( {
3283+ key : ONYX_KEYS . TEST_KEY ,
3284+ callback : ( value ) => {
3285+ normalValue = value ;
3286+ } ,
3287+ } ) ;
3288+ const connection2 = Onyx . connect ( {
3289+ key : ONYX_KEYS . RAM_ONLY_TEST_KEY ,
3290+ callback : ( value ) => {
3291+ ramOnlyValue = value ;
3292+ } ,
3293+ } ) ;
3294+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3295+
3296+ // Normal key should read from storage as expected
3297+ expect ( normalValue ) . toEqual ( 'normal_value' ) ;
3298+ // RAM-only key should NOT read stale value from storage
3299+ expect ( ramOnlyValue ) . toBeUndefined ( ) ;
3300+
3301+ Onyx . disconnect ( connection1 ) ;
3302+ Onyx . disconnect ( connection2 ) ;
3303+ } ) ;
3304+
3305+ it ( 'should not sync RAM-only keys from other instances via keepInstancesSync' , async ( ) => {
3306+ Onyx . init ( {
3307+ keys : ONYX_KEYS ,
3308+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3309+ shouldSyncMultipleInstances : true ,
3310+ } ) ;
3311+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3312+
3313+ // Get the callback that was passed to keepInstancesSync
3314+ const syncCallback = ( StorageMock . keepInstancesSync as jest . Mock ) . mock . calls [ 0 ] ?. [ 0 ] ;
3315+ expect ( syncCallback ) . toBeDefined ( ) ;
3316+
3317+ let receivedValue : unknown ;
3318+ const connection = Onyx . connect ( {
3319+ key : ONYX_KEYS . RAM_ONLY_TEST_KEY ,
3320+ callback : ( value ) => {
3321+ receivedValue = value ;
3322+ } ,
3323+ } ) ;
3324+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3325+
3326+ // Simulate another tab syncing a stale RAM-only key value
3327+ syncCallback ( ONYX_KEYS . RAM_ONLY_TEST_KEY , 'synced_stale_value' ) ;
3328+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3329+
3330+ // The RAM-only key should NOT have been updated from the sync
3331+ expect ( receivedValue ) . toBeUndefined ( ) ;
3332+ expect ( cache . get ( ONYX_KEYS . RAM_ONLY_TEST_KEY ) ) . toBeUndefined ( ) ;
3333+
3334+ // Verify that normal keys still sync correctly
3335+ let normalValue : unknown ;
3336+ const connection2 = Onyx . connect ( {
3337+ key : ONYX_KEYS . OTHER_TEST ,
3338+ callback : ( value ) => {
3339+ normalValue = value ;
3340+ } ,
3341+ } ) ;
3342+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3343+
3344+ syncCallback ( ONYX_KEYS . OTHER_TEST , 'synced_normal_value' ) ;
3345+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3346+
3347+ expect ( normalValue ) . toEqual ( 'synced_normal_value' ) ;
3348+
3349+ Onyx . disconnect ( connection ) ;
3350+ Onyx . disconnect ( connection2 ) ;
3351+ } ) ;
3352+
3353+ it ( 'should serve RAM-only keys from cache and normal keys from storage in multiGet' , async ( ) => {
3354+ const ramOnlyMember = `${ ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION } 1` ;
3355+ const normalMember = `${ ONYX_KEYS . COLLECTION . TEST_KEY } 1` ;
3356+
3357+ // Pre-seed storage with stale data for both normal and RAM-only keys
3358+ await StorageMock . setItem ( normalMember , 'normal_from_storage' ) ;
3359+ await StorageMock . setItem ( ramOnlyMember , { data : 'stale_collection_member' } ) ;
3360+
3361+ Onyx . init ( {
3362+ keys : ONYX_KEYS ,
3363+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3364+ } ) ;
3365+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3366+
3367+ // Set a RAM-only collection member via Onyx (goes to cache only)
3368+ await Onyx . set ( ramOnlyMember , { data : 'fresh_from_cache' } ) ;
3369+
3370+ // multiGet receives individual keys (e.g. collection members), not collection base keys
3371+ const result = await OnyxUtils . multiGet ( [ normalMember , ramOnlyMember ] ) ;
3372+
3373+ // Normal key should come from storage
3374+ expect ( result . get ( normalMember ) ) . toEqual ( 'normal_from_storage' ) ;
3375+ // RAM-only collection member should come from cache, not stale storage
3376+ expect ( result . get ( ramOnlyMember ) ) . toEqual ( { data : 'fresh_from_cache' } ) ;
3377+ } ) ;
3378+
3379+ it ( 'should return cached value for RAM-only key after set then connect' , async ( ) => {
3380+ await StorageMock . setItem ( ONYX_KEYS . RAM_ONLY_TEST_KEY , 'stale_value' ) ;
3381+
3382+ Onyx . init ( {
3383+ keys : ONYX_KEYS ,
3384+ ramOnlyKeys : [ ONYX_KEYS . RAM_ONLY_TEST_KEY , ONYX_KEYS . COLLECTION . RAM_ONLY_COLLECTION , ONYX_KEYS . RAM_ONLY_WITH_INITIAL_VALUE ] ,
3385+ } ) ;
3386+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3387+
3388+ // Write a fresh value to the RAM-only key
3389+ await Onyx . set ( ONYX_KEYS . RAM_ONLY_TEST_KEY , 'fresh_value' ) ;
3390+
3391+ let receivedValue : unknown ;
3392+ const connection = Onyx . connect ( {
3393+ key : ONYX_KEYS . RAM_ONLY_TEST_KEY ,
3394+ callback : ( value ) => {
3395+ receivedValue = value ;
3396+ } ,
3397+ } ) ;
3398+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
3399+
3400+ // Should get the fresh cached value, not the stale storage value
3401+ expect ( receivedValue ) . toEqual ( 'fresh_value' ) ;
3402+ expect ( cache . get ( ONYX_KEYS . RAM_ONLY_TEST_KEY ) ) . toEqual ( 'fresh_value' ) ;
3403+
3404+ // Verify storage was NOT written to
3405+ const storageValue = await StorageMock . getItem ( ONYX_KEYS . RAM_ONLY_TEST_KEY ) ;
3406+ expect ( storageValue ) . toEqual ( 'stale_value' ) ;
3407+
3408+ Onyx . disconnect ( connection ) ;
3409+ } ) ;
3410+ } ) ;
0 commit comments