@@ -75,6 +75,10 @@ const mockUsePermission = usePermission as jest.MockedFunction<
7575> ;
7676
7777describe ( 'LightspeedPage' , ( ) => {
78+ beforeEach ( ( ) => {
79+ localStorage . clear ( ) ;
80+ } ) ;
81+
7882 it ( 'should not display chatbot if permission checks are in loading phase' , async ( ) => {
7983 mockUsePermission . mockReturnValue ( { loading : true , allowed : true } ) ;
8084
@@ -153,4 +157,219 @@ describe('LightspeedPage', () => {
153157 "You'll no longer see this chat here. This will also delete related activity like prompts, responses, and feedback from your Lightspeed Activity." ,
154158 ) ;
155159 } ) ;
160+
161+ describe ( 'localStorage model persistence' , ( ) => {
162+ const LAST_SELECTED_MODEL_KEY = 'lastSelectedModel' ;
163+ const mockModels = [
164+ {
165+ provider_resource_id : 'model-1' ,
166+ provider_id : 'provider-1' ,
167+ model_type : 'llm' ,
168+ } ,
169+ {
170+ provider_resource_id : 'model-2' ,
171+ provider_id : 'provider-2' ,
172+ model_type : 'llm' ,
173+ } ,
174+ {
175+ provider_resource_id : 'model-3' ,
176+ provider_id : 'provider-3' ,
177+ model_type : 'llm' ,
178+ } ,
179+ ] ;
180+
181+ beforeEach ( ( ) => {
182+ mockUsePermission . mockReturnValue ( { loading : false , allowed : true } ) ;
183+ const { useAllModels } = require ( '../../hooks/useAllModels' ) ;
184+ ( useAllModels as jest . Mock ) . mockReturnValue ( {
185+ data : mockModels ,
186+ isLoading : false ,
187+ isError : false ,
188+ error : null ,
189+ } ) ;
190+ } ) ;
191+
192+ it ( 'should load last selected model from localStorage on mount' , async ( ) => {
193+ const storedData = JSON . stringify ( {
194+ model : 'model-2' ,
195+ provider : 'provider-2' ,
196+ } ) ;
197+ localStorage . setItem ( LAST_SELECTED_MODEL_KEY , storedData ) ;
198+
199+ await renderInTestApp (
200+ < TestApiProvider
201+ apis = { [
202+ [ identityApiRef , identityApi ] ,
203+ [ lightspeedApiRef , mockLightspeedApi ] ,
204+ ] }
205+ >
206+ < LightspeedPage />
207+ </ TestApiProvider > ,
208+ ) ;
209+
210+ await waitFor ( ( ) => {
211+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
212+ } ) ;
213+
214+ // Verify the stored model was loaded
215+ const storedValue = localStorage . getItem ( LAST_SELECTED_MODEL_KEY ) ;
216+ expect ( storedValue ) . toBe ( storedData ) ;
217+ } ) ;
218+
219+ it ( 'should fallback to first model if stored model does not exist' , async ( ) => {
220+ const storedData = JSON . stringify ( {
221+ model : 'non-existent-model' ,
222+ provider : 'non-existent-provider' ,
223+ } ) ;
224+ localStorage . setItem ( LAST_SELECTED_MODEL_KEY , storedData ) ;
225+
226+ await renderInTestApp (
227+ < TestApiProvider
228+ apis = { [
229+ [ identityApiRef , identityApi ] ,
230+ [ lightspeedApiRef , mockLightspeedApi ] ,
231+ ] }
232+ >
233+ < LightspeedPage />
234+ </ TestApiProvider > ,
235+ ) ;
236+
237+ await waitFor ( ( ) => {
238+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
239+ } ) ;
240+
241+ // Should fallback to first model and save it
242+ await waitFor ( ( ) => {
243+ const storedValue = localStorage . getItem ( LAST_SELECTED_MODEL_KEY ) ;
244+ const parsed = storedValue ? JSON . parse ( storedValue ) : null ;
245+ expect ( parsed ?. model ) . toBe ( 'model-1' ) ;
246+ expect ( parsed ?. provider ) . toBe ( 'provider-1' ) ;
247+ } ) ;
248+ } ) ;
249+
250+ it ( 'should save model to localStorage when model is selected' , async ( ) => {
251+ await renderInTestApp (
252+ < TestApiProvider
253+ apis = { [
254+ [ identityApiRef , identityApi ] ,
255+ [ lightspeedApiRef , mockLightspeedApi ] ,
256+ ] }
257+ >
258+ < LightspeedPage />
259+ </ TestApiProvider > ,
260+ ) ;
261+
262+ await waitFor ( ( ) => {
263+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
264+ } ) ;
265+
266+ // Should save first model by default
267+ await waitFor ( ( ) => {
268+ const storedValue = localStorage . getItem ( LAST_SELECTED_MODEL_KEY ) ;
269+ const parsed = storedValue ? JSON . parse ( storedValue ) : null ;
270+ expect ( parsed ?. model ) . toBe ( 'model-1' ) ;
271+ expect ( parsed ?. provider ) . toBe ( 'provider-1' ) ;
272+ } ) ;
273+ } ) ;
274+
275+ it ( 'should handle localStorage errors gracefully when loading' , async ( ) => {
276+ // Mock localStorage.getItem to throw an error for the specific key
277+ const originalGetItem = localStorage . getItem ;
278+ const getItemSpy = jest . fn ( ( key : string ) => {
279+ if ( key === LAST_SELECTED_MODEL_KEY ) {
280+ throw new Error ( 'localStorage error' ) ;
281+ }
282+ return originalGetItem . call ( localStorage , key ) ;
283+ } ) ;
284+ localStorage . getItem = getItemSpy ;
285+
286+ await renderInTestApp (
287+ < TestApiProvider
288+ apis = { [
289+ [ identityApiRef , identityApi ] ,
290+ [ lightspeedApiRef , mockLightspeedApi ] ,
291+ ] }
292+ >
293+ < LightspeedPage />
294+ </ TestApiProvider > ,
295+ ) ;
296+
297+ await waitFor ( ( ) => {
298+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
299+ } ) ;
300+
301+ // Restore before checking localStorage
302+ localStorage . getItem = originalGetItem ;
303+
304+ // Should still render and fallback to first model (saved after error)
305+ // This verifies that the component handles the error gracefully
306+ await waitFor ( ( ) => {
307+ const storedValue = localStorage . getItem ( LAST_SELECTED_MODEL_KEY ) ;
308+ const parsed = storedValue ? JSON . parse ( storedValue ) : null ;
309+ expect ( parsed ?. model ) . toBe ( 'model-1' ) ;
310+ expect ( parsed ?. provider ) . toBe ( 'provider-1' ) ;
311+ } ) ;
312+ } ) ;
313+
314+ it ( 'should handle localStorage errors gracefully when saving' , async ( ) => {
315+ // Mock localStorage.setItem to throw an error for the specific key
316+ const originalSetItem = localStorage . setItem ;
317+ const setItemSpy = jest . fn ( ( key : string , value : string ) => {
318+ if ( key === LAST_SELECTED_MODEL_KEY ) {
319+ throw new Error ( 'localStorage setItem error' ) ;
320+ }
321+ return originalSetItem . call ( localStorage , key , value ) ;
322+ } ) ;
323+ localStorage . setItem = setItemSpy ;
324+
325+ await renderInTestApp (
326+ < TestApiProvider
327+ apis = { [
328+ [ identityApiRef , identityApi ] ,
329+ [ lightspeedApiRef , mockLightspeedApi ] ,
330+ ] }
331+ >
332+ < LightspeedPage />
333+ </ TestApiProvider > ,
334+ ) ;
335+
336+ await waitFor ( ( ) => {
337+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
338+ } ) ;
339+
340+ // Should still render despite save error
341+ // This verifies that the component handles the error gracefully
342+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
343+
344+ // Restore
345+ localStorage . setItem = originalSetItem ;
346+ } ) ;
347+
348+ it ( 'should use first model when localStorage is empty' , async ( ) => {
349+ localStorage . removeItem ( LAST_SELECTED_MODEL_KEY ) ;
350+
351+ await renderInTestApp (
352+ < TestApiProvider
353+ apis = { [
354+ [ identityApiRef , identityApi ] ,
355+ [ lightspeedApiRef , mockLightspeedApi ] ,
356+ ] }
357+ >
358+ < LightspeedPage />
359+ </ TestApiProvider > ,
360+ ) ;
361+
362+ await waitFor ( ( ) => {
363+ expect ( screen . getByText ( 'LightspeedChat' ) ) . toBeInTheDocument ( ) ;
364+ } ) ;
365+
366+ // Should save first model
367+ await waitFor ( ( ) => {
368+ const storedValue = localStorage . getItem ( LAST_SELECTED_MODEL_KEY ) ;
369+ const parsed = storedValue ? JSON . parse ( storedValue ) : null ;
370+ expect ( parsed ?. model ) . toBe ( 'model-1' ) ;
371+ expect ( parsed ?. provider ) . toBe ( 'provider-1' ) ;
372+ } ) ;
373+ } ) ;
374+ } ) ;
156375} ) ;
0 commit comments