import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { useSessionsStore } from '@/stores/sessions.js'
vi.mock('@/api/index.js', () => ({
getSessions: vi.fn(),
createSession: vi.fn(),
deleteSession: vi.fn(),
pinSession: vi.fn(),
}))
import * as api from '@/api/index.js'
describe('Sessions store', () => {
beforeEach(() => {
setActivePinia(createPinia())
vi.clearAllMocks()
})
it('fetchSessions loads sessions', async () => {
api.getSessions.mockResolvedValue({
items: [{ session_id: 's1', profile_id: 'secretary', pinned: false }],
has_more: true,
next_offset: 1,
})
const store = useSessionsStore()
await store.fetchSessions('developer')
expect(api.getSessions).toHaveBeenCalledWith({ limit: 30, offset: 0, profileId: 'developer', search: null })
expect(store.sessions).toHaveLength(1)
expect(store.sessions[0].session_id).toBe('s1')
expect(store.hasMore).toBe(true)
expect(store.currentProfileId).toBe('developer')
expect(store.loading).toBe(false)
})
it('fetchMoreSessions appends the next page', async () => {
api.getSessions
.mockResolvedValueOnce({
items: [{ session_id: 's1', profile_id: 'secretary', pinned: false }],
has_more: true,
next_offset: 1,
})
.mockResolvedValueOnce({
items: [{ session_id: 's2', profile_id: 'developer', pinned: false }],
has_more: false,
next_offset: 2,
})
const store = useSessionsStore()
await store.fetchSessions('developer')
await store.fetchMoreSessions()
expect(api.getSessions).toHaveBeenLastCalledWith({ limit: 30, offset: 1, profileId: 'developer', search: null })
expect(store.sessions.map(s => s.session_id)).toEqual(['s1', 's2'])
expect(store.hasMore).toBe(false)
expect(store.loadingMore).toBe(false)
})
it('fetchMoreSessions does nothing without more pages', async () => {
api.getSessions.mockResolvedValue({
items: [],
has_more: false,
next_offset: 0,
})
const store = useSessionsStore()
await store.fetchSessions()
await store.fetchMoreSessions()
expect(api.getSessions).toHaveBeenCalledTimes(1)
})
it('createSession prepends placeholder and returns session', async () => {
api.createSession.mockResolvedValue({
session_id: 's2',
profile_id: 'developer',
created_at: '2024-01-01T00:00:00Z',
})
const store = useSessionsStore()
const result = await store.createSession('developer')
expect(api.createSession).toHaveBeenCalledWith('developer')
expect(store.sessions).toHaveLength(1)
expect(store.sessions[0].session_id).toBe('s2')
expect(store.sessions[0].message_count).toBe(0)
expect(result.session_id).toBe('s2')
})
it('deleteSession filters out the session', async () => {
api.deleteSession.mockResolvedValue()
const store = useSessionsStore()
store.sessions = [
{ session_id: 's1' },
{ session_id: 's2' },
]
await store.deleteSession('s1')
expect(api.deleteSession).toHaveBeenCalledWith('s1')
expect(store.sessions).toHaveLength(1)
expect(store.sessions[0].session_id).toBe('s2')
})
it('pinSession sorts pinned to top', async () => {
api.pinSession.mockResolvedValue()
const store = useSessionsStore()
store.sessions = [
{ session_id: 's1', pinned: false },
{ session_id: 's2', pinned: false },
]
await store.pinSession('s2', true)
expect(api.pinSession).toHaveBeenCalledWith('s2', true)
expect(store.sessions[0].session_id).toBe('s2')
expect(store.sessions[0].pinned).toBe(true)
})
it('updatePreview mutates session preview', () => {
const store = useSessionsStore()
store.sessions = [{ session_id: 's1', preview: '' }]
store.updatePreview('s1', 'hello')
expect(store.sessions[0].preview).toBe('hello')
})
it('updateName mutates session name', () => {
const store = useSessionsStore()
store.sessions = [{ session_id: 's1', name: null }]
store.updateName('s1', 'Work')
expect(store.sessions[0].name).toBe('Work')
})
it('refetch merges into existing items, preserving references', async () => {
api.getSessions.mockResolvedValueOnce({
items: [{ session_id: 's1', preview: 'old', pinned: false }],
has_more: false,
next_offset: 1,
})
const store = useSessionsStore()
await store.fetchSessions('developer')
const ref = store.sessions[0]
api.getSessions.mockResolvedValueOnce({
items: [{ session_id: 's1', preview: 'new', pinned: false }],
has_more: false,
next_offset: 1,
})
await store.fetchSessions('developer')
expect(store.sessions[0]).toBe(ref) // same object reference — no row remount
expect(store.sessions[0].preview).toBe('new')
})
it('refetch follows server order and drops sessions removed on the server', async () => {
api.getSessions
.mockResolvedValueOnce({
items: [{ session_id: 's1' }, { session_id: 's2' }],
has_more: false,
next_offset: 2,
})
.mockResolvedValueOnce({
items: [{ session_id: 's2' }, { session_id: 's3' }],
has_more: false,
next_offset: 2,
})
const store = useSessionsStore()
await store.fetchSessions('developer')
const ref2 = store.sessions[1]
await store.fetchSessions('developer')
expect(store.sessions.map(s => s.session_id)).toEqual(['s2', 's3'])
expect(store.sessions[0]).toBe(ref2)
})
it('pinSession keeps item reference (in-place update + sort)', async () => {
api.pinSession.mockResolvedValue()
const store = useSessionsStore()
store.sessions = [
{ session_id: 's1', pinned: false },
{ session_id: 's2', pinned: false },
]
const ref2 = store.sessions[1]
await store.pinSession('s2', true)
expect(store.sessions[0].session_id).toBe('s2')
expect(store.sessions[0]).toBe(ref2)
expect(store.sessions[0].pinned).toBe(true)
})
it('createSession places the placeholder where the server list would (below pinned)', async () => {
api.createSession.mockResolvedValue({
session_id: 'new1',
profile_id: 'developer',
created_at: '2024-01-02T00:00:00Z',
})
const store = useSessionsStore()
store.sessions = [{ session_id: 's1', pinned: true }]
await store.createSession('developer')
// placeholder lands after the pinned run, matching server's
// pinned DESC, last_active DESC ordering
expect(store.sessions.map(s => s.session_id)).toEqual(['s1', 'new1'])
})
it('rememberName persists names beyond the loaded page; fetchSessions fills the map', async () => {
api.getSessions.mockResolvedValueOnce({
items: [{ session_id: 's1', name: 'Work', preview: '', pinned: false }],
has_more: false,
next_offset: 1,
})
const store = useSessionsStore()
await store.fetchSessions('developer')
expect(store.nameById['s1']).toBe('Work')
store.rememberName('s2', 'Deep session')
expect(store.nameById['s2']).toBe('Deep session')
api.getSessions.mockResolvedValueOnce({
items: [{ session_id: 's3', name: null, preview: '', pinned: false }],
has_more: false,
next_offset: 1,
})
await store.fetchSessions('developer')
// null name must not wipe remembered names
expect(store.nameById['s2']).toBe('Deep session')
store.updateName('s3', 'Named now')
expect(store.nameById['s3']).toBe('Named now')
})
})