11import { describe , expect , it , vi } from 'vitest'
22
3- const setupStore = async ( ) => {
3+ type SetupStoreOptions = {
4+ initialSettings ?: Record < string , unknown >
5+ failGetSetting ?: boolean
6+ failSetSetting ?: boolean
7+ }
8+
9+ const SIDEBAR_GROUP_MODE_KEY = 'sidebar_group_mode'
10+
11+ const setupStore = async ( options : SetupStoreOptions = { } ) => {
412 vi . resetModules ( )
513
614 const agentSessionPresenter = {
@@ -25,14 +33,33 @@ const setupStore = async () => {
2533 goToNewThread : vi . fn ( ) ,
2634 currentRoute : 'chat'
2735 }
36+ const settings = { ...( options . initialSettings ?? { } ) }
37+ const configPresenter = {
38+ getSetting : vi . fn ( async < T > ( key : string ) => {
39+ if ( options . failGetSetting ) {
40+ throw new Error ( 'failed to read setting' )
41+ }
42+ return settings [ key ] as T | undefined
43+ } ) ,
44+ setSetting : vi . fn ( async < T > ( key : string , value : T ) => {
45+ if ( options . failSetSetting ) {
46+ throw new Error ( 'failed to write setting' )
47+ }
48+ settings [ key ] = value
49+ } )
50+ }
2851 const listeners = new Map < string , Array < ( ...args : any [ ] ) => void > > ( )
2952
3053 vi . doMock ( 'pinia' , ( ) => ( {
3154 defineStore : ( _id : string , setup : ( ) => unknown ) => setup
3255 } ) )
3356
3457 vi . doMock ( '@/composables/usePresenter' , ( ) => ( {
35- usePresenter : ( name : string ) => ( name === 'tabPresenter' ? tabPresenter : agentSessionPresenter )
58+ usePresenter : ( name : string ) => {
59+ if ( name === 'tabPresenter' ) return tabPresenter
60+ if ( name === 'configPresenter' ) return configPresenter
61+ return agentSessionPresenter
62+ }
3663 } ) )
3764
3865 vi . doMock ( '@/stores/ui/pageRouter' , ( ) => ( {
@@ -67,12 +94,26 @@ const setupStore = async () => {
6794 handler ( undefined , payload )
6895 }
6996 }
70- return { store, clearStreamingState, agentSessionPresenter, pageRouter, emitIpc, SESSION_EVENTS }
97+ return {
98+ store,
99+ settings,
100+ configPresenter,
101+ clearStreamingState,
102+ agentSessionPresenter,
103+ pageRouter,
104+ emitIpc,
105+ SESSION_EVENTS
106+ }
71107}
72108
73109describe ( 'sessionStore.getFilteredGroups' , ( ) => {
74110 it ( 'hides draft sessions from grouped sidebar lists' , async ( ) => {
75- const { store } = await setupStore ( )
111+ const { store } = await setupStore ( {
112+ initialSettings : {
113+ [ SIDEBAR_GROUP_MODE_KEY ] : 'time'
114+ }
115+ } )
116+ await store . fetchSessions ( )
76117 const now = Date . now ( )
77118
78119 store . sessions . value = [
@@ -152,6 +193,123 @@ describe('sessionStore.getFilteredGroups', () => {
152193 expect ( groupIds ) . toEqual ( [ 'normal-1' ] )
153194 expect ( pinnedIds ) . toEqual ( [ 'pinned-1' ] )
154195 } )
196+
197+ it ( 'uses the last path segment for Windows project labels' , async ( ) => {
198+ const { store } = await setupStore ( )
199+ const now = Date . now ( )
200+
201+ await store . fetchSessions ( )
202+ store . sessions . value = [
203+ {
204+ id : 'windows-1' ,
205+ title : 'Windows Chat' ,
206+ agentId : 'deepchat' ,
207+ status : 'none' ,
208+ projectDir : 'C:\\Users\\DeepChat\\workspace' ,
209+ providerId : 'openai' ,
210+ modelId : 'gpt-4' ,
211+ isPinned : false ,
212+ isDraft : false ,
213+ createdAt : now ,
214+ updatedAt : now
215+ }
216+ ]
217+
218+ const groups = store . getFilteredGroups ( null )
219+
220+ expect ( groups ) . toHaveLength ( 1 )
221+ expect ( groups [ 0 ] ?. label ) . toBe ( 'workspace' )
222+ } )
223+ } )
224+
225+ describe ( 'sessionStore group mode preferences' , ( ) => {
226+ it ( 'falls back to project when no saved preference exists' , async ( ) => {
227+ const { store } = await setupStore ( )
228+
229+ await store . fetchSessions ( )
230+
231+ expect ( store . groupMode . value ) . toBe ( 'project' )
232+ } )
233+
234+ it ( 'restores the saved group mode preference' , async ( ) => {
235+ const { store } = await setupStore ( {
236+ initialSettings : {
237+ [ SIDEBAR_GROUP_MODE_KEY ] : 'time'
238+ }
239+ } )
240+
241+ await store . fetchSessions ( )
242+
243+ expect ( store . groupMode . value ) . toBe ( 'time' )
244+ } )
245+
246+ it ( 'falls back to project when the saved preference is invalid' , async ( ) => {
247+ const { store } = await setupStore ( {
248+ initialSettings : {
249+ [ SIDEBAR_GROUP_MODE_KEY ] : 'invalid-mode'
250+ }
251+ } )
252+
253+ await store . fetchSessions ( )
254+
255+ expect ( store . groupMode . value ) . toBe ( 'project' )
256+ } )
257+
258+ it ( 'persists toggled group mode changes' , async ( ) => {
259+ const { store, settings, configPresenter } = await setupStore ( )
260+
261+ await store . fetchSessions ( )
262+ await store . toggleGroupMode ( )
263+
264+ expect ( store . groupMode . value ) . toBe ( 'time' )
265+ expect ( configPresenter . setSetting ) . toHaveBeenCalledWith ( SIDEBAR_GROUP_MODE_KEY , 'time' )
266+ expect ( settings [ SIDEBAR_GROUP_MODE_KEY ] ) . toBe ( 'time' )
267+ } )
268+
269+ it ( 'rolls back the group mode when persistence fails' , async ( ) => {
270+ const { store, configPresenter } = await setupStore ( {
271+ failSetSetting : true
272+ } )
273+
274+ await store . fetchSessions ( )
275+ await store . toggleGroupMode ( )
276+
277+ expect ( store . groupMode . value ) . toBe ( 'project' )
278+ expect ( configPresenter . setSetting ) . toHaveBeenCalledWith ( SIDEBAR_GROUP_MODE_KEY , 'time' )
279+ } )
280+
281+ it ( 'serializes concurrent group mode writes and persists the last toggle' , async ( ) => {
282+ const { store, settings, configPresenter } = await setupStore ( )
283+ const pendingResolvers : Array < ( ) => void > = [ ]
284+
285+ await store . fetchSessions ( )
286+ configPresenter . setSetting . mockImplementation ( async < T > ( key : string , value : T ) => {
287+ await new Promise < void > ( ( resolve ) => {
288+ pendingResolvers . push ( ( ) => {
289+ settings [ key ] = value
290+ resolve ( )
291+ } )
292+ } )
293+ } )
294+
295+ const firstToggle = store . toggleGroupMode ( )
296+ const secondToggle = store . toggleGroupMode ( )
297+
298+ await Promise . resolve ( )
299+
300+ expect ( store . groupMode . value ) . toBe ( 'project' )
301+ expect ( configPresenter . setSetting ) . toHaveBeenCalledTimes ( 1 )
302+
303+ pendingResolvers . shift ( ) ?.( )
304+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) )
305+
306+ expect ( configPresenter . setSetting ) . toHaveBeenCalledTimes ( 2 )
307+
308+ pendingResolvers . shift ( ) ?.( )
309+ await Promise . all ( [ firstToggle , secondToggle ] )
310+
311+ expect ( settings [ SIDEBAR_GROUP_MODE_KEY ] ) . toBe ( 'project' )
312+ } )
155313} )
156314
157315describe ( 'sessionStore streaming cleanup' , ( ) => {
0 commit comments