@@ -35,6 +35,212 @@ beforeEach(async () => {
3535} ) ;
3636
3737describe ( 'useOnyx' , ( ) => {
38+ describe ( 'dynamic key' , ( ) => {
39+ beforeEach ( ( ) => {
40+ jest . spyOn ( console , 'error' ) . mockImplementation ( jest . fn ) ;
41+ } ) ;
42+
43+ afterEach ( ( ) => {
44+ ( console . error as unknown as jest . SpyInstance < void , Parameters < typeof console . error > > ) . mockRestore ( ) ;
45+ } ) ;
46+
47+ it ( 'should not throw any errors when changing from a collection member key to another one' , async ( ) => {
48+ const { rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
49+
50+ try {
51+ await act ( async ( ) => {
52+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
53+ } ) ;
54+ } catch ( e ) {
55+ fail ( 'Expected to not throw any errors.' ) ;
56+ }
57+ } ) ;
58+
59+ it ( 'should transition through loading when switching between collection member keys that both resolve to undefined' , async ( ) => {
60+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
61+
62+ // Wait for initial key to fully load
63+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
64+
65+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
66+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
67+
68+ // Switch to another collection member key that also has no data
69+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
70+
71+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
72+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loading' ) ;
73+
74+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
75+
76+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
77+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
78+ } ) ;
79+
80+ it ( 'should return cached value immediately with loaded status when switching to a key that has data' , async ( ) => {
81+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` , 'test_value' ) ;
82+
83+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
84+
85+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
86+
87+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
88+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
89+
90+ // Switch to a key that has cached data
91+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
92+
93+ // Value and loaded status should be available synchronously, without waiting for promises
94+ expect ( result . current [ 0 ] ) . toEqual ( 'test_value' ) ;
95+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
96+
97+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
98+
99+ expect ( result . current [ 0 ] ) . toEqual ( 'test_value' ) ;
100+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
101+ } ) ;
102+
103+ it ( 'should clear previous data and transition through loading when switching from a key with data to one without' , async ( ) => {
104+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` , 'initial_value' ) ;
105+
106+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
107+
108+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
109+
110+ expect ( result . current [ 0 ] ) . toEqual ( 'initial_value' ) ;
111+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
112+
113+ // Switch to a key that has no data
114+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
115+
116+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
117+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loading' ) ;
118+
119+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
120+
121+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
122+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
123+ } ) ;
124+
125+ it ( 'should return the new value when switching from a key with data to another key with different data' , async ( ) => {
126+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` , 'value_one' ) ;
127+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` , 'value_two' ) ;
128+
129+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
130+
131+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
132+
133+ expect ( result . current [ 0 ] ) . toEqual ( 'value_one' ) ;
134+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
135+
136+ // Switch to another key that also has data
137+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
138+
139+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
140+
141+ expect ( result . current [ 0 ] ) . toEqual ( 'value_two' ) ;
142+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
143+ } ) ;
144+
145+ it ( 'should apply the selector against the new key data when switching keys' , async ( ) => {
146+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` , { id : 'entry1_id' , name : 'entry1_name' } ) ;
147+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` , { id : 'entry2_id' , name : 'entry2_name' } ) ;
148+
149+ const selector = ( ( entry : OnyxEntry < { id : string ; name : string } > ) => entry ?. name ) as UseOnyxSelector < OnyxKey , string | undefined > ;
150+
151+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key , { selector} ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
152+
153+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
154+
155+ expect ( result . current [ 0 ] ) . toEqual ( 'entry1_name' ) ;
156+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
157+
158+ // Switch key — selector should run against the new key's data
159+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
160+
161+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
162+
163+ expect ( result . current [ 0 ] ) . toEqual ( 'entry2_name' ) ;
164+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
165+ } ) ;
166+
167+ it ( 'should handle rapid key switching and settle on the final key value' , async ( ) => {
168+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` , 'value_one' ) ;
169+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` , 'value_two' ) ;
170+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 3` , 'value_three' ) ;
171+
172+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
173+
174+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
175+
176+ expect ( result . current [ 0 ] ) . toEqual ( 'value_one' ) ;
177+
178+ // Rapidly switch keys without waiting for promises between switches
179+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
180+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 3` ) ;
181+
182+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
183+
184+ expect ( result . current [ 0 ] ) . toEqual ( 'value_three' ) ;
185+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
186+ } ) ;
187+
188+ it ( 'should correctly switch between non-collection keys' , async ( ) => {
189+ Onyx . set ( ONYXKEYS . TEST_KEY , 'value_one' ) ;
190+ Onyx . set ( ONYXKEYS . TEST_KEY_2 , 'value_two' ) ;
191+
192+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : ONYXKEYS . TEST_KEY as string } ) ;
193+
194+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
195+
196+ expect ( result . current [ 0 ] ) . toEqual ( 'value_one' ) ;
197+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
198+
199+ rerender ( ONYXKEYS . TEST_KEY_2 ) ;
200+
201+ // Cached value should be available synchronously
202+ expect ( result . current [ 0 ] ) . toEqual ( 'value_two' ) ;
203+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
204+
205+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
206+
207+ expect ( result . current [ 0 ] ) . toEqual ( 'value_two' ) ;
208+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
209+ } ) ;
210+
211+ it ( 'should correctly return to a previously used key (A → B → A round-trip)' , async ( ) => {
212+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` , 'value_one' ) ;
213+ Onyx . set ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` , 'value_two' ) ;
214+
215+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key ) , { initialProps : `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` as string } ) ;
216+
217+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
218+
219+ expect ( result . current [ 0 ] ) . toEqual ( 'value_one' ) ;
220+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
221+
222+ // A → B
223+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 2` ) ;
224+
225+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
226+
227+ expect ( result . current [ 0 ] ) . toEqual ( 'value_two' ) ;
228+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
229+
230+ // B → A
231+ rerender ( `${ ONYXKEYS . COLLECTION . TEST_KEY } 1` ) ;
232+
233+ // Cached value should be available synchronously
234+ expect ( result . current [ 0 ] ) . toEqual ( 'value_one' ) ;
235+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
236+
237+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
238+
239+ expect ( result . current [ 0 ] ) . toEqual ( 'value_one' ) ;
240+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
241+ } ) ;
242+ } ) ;
243+
38244 describe ( 'misc' , ( ) => {
39245 it ( 'should initially return loading state while loading non-existent key, and then return `undefined` and loaded state' , async ( ) => {
40246 const { result} = renderHook ( ( ) => useOnyx ( ONYXKEYS . TEST_KEY ) ) ;
@@ -711,6 +917,33 @@ describe('useOnyx', () => {
711917 expect ( result . current [ 0 ] ) . toEqual ( 'test_selected' ) ;
712918 expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
713919 } ) ;
920+
921+ it ( 'should suppress stored values for the new key when switching keys with initWithStoredValues: false' , async ( ) => {
922+ await StorageMock . setItem ( ONYXKEYS . TEST_KEY , 'stored_value_one' ) ;
923+ await StorageMock . setItem ( ONYXKEYS . TEST_KEY_2 , 'stored_value_two' ) ;
924+
925+ const { result, rerender} = renderHook ( ( key : string ) => useOnyx ( key , { initWithStoredValues : false } ) , { initialProps : ONYXKEYS . TEST_KEY as string } ) ;
926+
927+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
928+
929+ // initWithStoredValues: false — stored value should be suppressed
930+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
931+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
932+
933+ rerender ( ONYXKEYS . TEST_KEY_2 ) ;
934+
935+ await act ( async ( ) => waitForPromisesToResolve ( ) ) ;
936+
937+ // Stored value for the new key should also be suppressed
938+ expect ( result . current [ 0 ] ) . toBeUndefined ( ) ;
939+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
940+
941+ // But live updates should still come through
942+ await act ( async ( ) => Onyx . merge ( ONYXKEYS . TEST_KEY_2 , 'live_value' ) ) ;
943+
944+ expect ( result . current [ 0 ] ) . toEqual ( 'live_value' ) ;
945+ expect ( result . current [ 1 ] . status ) . toEqual ( 'loaded' ) ;
946+ } ) ;
714947 } ) ;
715948
716949 describe ( 'multiple usage' , ( ) => {
0 commit comments