import { describe, expect, it } from 'vitest' import { appendMissingOrderKeys, applyStoredOrdering, buildSidebarProjectsFromWorkspaces, } from './use-sidebar-workspaces-list' import type { WorkspaceDescriptor } from '@/stores/session-store' interface OrderedItem { key: string } function item(key: string): OrderedItem { return { key } } function workspace( input: Pick & Partial< Pick< WorkspaceDescriptor, "projectDisplayName" | "projectRootPath" | "projectKind" | "workspaceKind" > > ): WorkspaceDescriptor { return { id: input.id, projectId: input.projectId, projectDisplayName: input.projectDisplayName ?? input.projectId, projectRootPath: input.projectRootPath ?? input.id, projectKind: input.projectKind ?? "git", workspaceKind: input.workspaceKind ?? "local_checkout", name: input.name, status: input.status, activityAt: input.activityAt, diffStat: null, } } describe('applyStoredOrdering', () => { it('keeps unknown items on the baseline while applying stored order', () => { const result = applyStoredOrdering({ items: [item('new'), item('a'), item('b')], storedOrder: ['b', 'a'], getKey: (entry) => entry.key, }) expect(result.map((entry) => entry.key)).toEqual(['new', 'b', 'a']) }) it('ignores stale and duplicate stored keys', () => { const result = applyStoredOrdering({ items: [item('x'), item('y')], storedOrder: ['missing', 'y', 'y', 'x'], getKey: (entry) => entry.key, }) expect(result.map((entry) => entry.key)).toEqual(['y', 'x']) }) it('returns baseline when there is no persisted order', () => { const baseline = [item('first'), item('second')] const result = applyStoredOrdering({ items: baseline, storedOrder: [], getKey: (entry) => entry.key, }) expect(result).toBe(baseline) }) }) describe('appendMissingOrderKeys', () => { it('appends unseen keys while preserving existing order', () => { const result = appendMissingOrderKeys({ currentOrder: ['project-b', 'project-a'], visibleKeys: ['project-a', 'project-b', 'project-c'], }) expect(result).toEqual(['project-b', 'project-a', 'project-c']) }) it('returns the same array when there are no unseen keys', () => { const currentOrder = ['project-a', 'project-b'] const result = appendMissingOrderKeys({ currentOrder, visibleKeys: ['project-b', 'project-a'], }) expect(result).toBe(currentOrder) }) }) describe('buildSidebarProjectsFromWorkspaces', () => { it('uses workspace descriptor name and status directly', () => { const workspaces: WorkspaceDescriptor[] = [ workspace({ id: '/repo/main', projectId: 'project-1', name: 'feat/hard-cut', status: 'failed', activityAt: new Date('2026-01-01T00:00:00.000Z'), }), ] const projects = buildSidebarProjectsFromWorkspaces({ serverId: 'srv', workspaces, projectOrder: [], workspaceOrderByScope: {}, }) expect(projects).toHaveLength(1) expect(projects[0]?.statusBucket).toBe('failed') expect(projects[0]?.workspaces[0]?.name).toBe('feat/hard-cut') expect(projects[0]?.workspaces[0]?.statusBucket).toBe('failed') }) it('preserves stored project order even when activity changes', () => { const initialWorkspaces: WorkspaceDescriptor[] = [ workspace({ id: '/repo/b', projectId: 'project-b', name: 'feat/b', status: 'running', activityAt: new Date('2026-01-02T00:00:00.000Z'), }), workspace({ id: '/repo/a', projectId: 'project-a', name: 'feat/a', status: 'running', activityAt: new Date('2026-01-01T00:00:00.000Z'), }), ] const seededOrder = appendMissingOrderKeys({ currentOrder: [], visibleKeys: buildSidebarProjectsFromWorkspaces({ serverId: 'srv', workspaces: initialWorkspaces, projectOrder: [], workspaceOrderByScope: {}, }).map((project) => project.projectKey), }) const updatedProjects = buildSidebarProjectsFromWorkspaces({ serverId: 'srv', workspaces: [ workspace({ id: '/repo/b', projectId: 'project-b', name: 'feat/b', status: 'running', activityAt: new Date('2026-01-02T00:00:00.000Z'), }), workspace({ id: '/repo/a', projectId: 'project-a', name: 'feat/a', status: 'running', activityAt: new Date('2026-01-03T00:00:00.000Z'), }), ], projectOrder: seededOrder, workspaceOrderByScope: {}, }) expect(updatedProjects.map((project) => project.projectKey)).toEqual(['project-b', 'project-a']) }) it('appends new projects after the stored project order', () => { const projects = buildSidebarProjectsFromWorkspaces({ serverId: 'srv', workspaces: [ workspace({ id: '/repo/c', projectId: 'project-c', name: 'feat/c', status: 'running', activityAt: new Date('2026-01-04T00:00:00.000Z'), }), workspace({ id: '/repo/b', projectId: 'project-b', name: 'feat/b', status: 'running', activityAt: new Date('2026-01-02T00:00:00.000Z'), }), workspace({ id: '/repo/a', projectId: 'project-a', name: 'feat/a', status: 'running', activityAt: new Date('2026-01-01T00:00:00.000Z'), }), ], projectOrder: ['project-b', 'project-a', 'project-c'], workspaceOrderByScope: {}, }) expect(projects.map((project) => project.projectKey)).toEqual(['project-b', 'project-a', 'project-c']) }) it('preserves stored workspace order when workspace activity changes', () => { const initialProjects = buildSidebarProjectsFromWorkspaces({ serverId: 'srv', workspaces: [ workspace({ id: '/repo/main', projectId: 'project-1', name: 'main', status: 'running', activityAt: new Date('2026-01-02T00:00:00.000Z'), }), workspace({ id: '/repo/feature', projectId: 'project-1', name: 'feature', status: 'running', activityAt: new Date('2026-01-01T00:00:00.000Z'), }), ], projectOrder: ['project-1'], workspaceOrderByScope: {}, }) const seededWorkspaceOrder = appendMissingOrderKeys({ currentOrder: [], visibleKeys: initialProjects[0]?.workspaces.map((workspace) => workspace.workspaceKey) ?? [], }) const projects = buildSidebarProjectsFromWorkspaces({ serverId: 'srv', workspaces: [ workspace({ id: '/repo/main', projectId: 'project-1', name: 'main', status: 'running', activityAt: new Date('2026-01-02T00:00:00.000Z'), }), workspace({ id: '/repo/feature', projectId: 'project-1', name: 'feature', status: 'running', activityAt: new Date('2026-01-03T00:00:00.000Z'), }), ], projectOrder: ['project-1'], workspaceOrderByScope: { 'srv::project-1': seededWorkspaceOrder, }, }) expect(projects[0]?.workspaces.map((workspace) => workspace.workspaceId)).toEqual([ '/repo/main', '/repo/feature', ]) }) })