264 lines
7.5 KiB
TypeScript
264 lines
7.5 KiB
TypeScript
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<WorkspaceDescriptor, "id" | "projectId" | "name" | "status" | "activityAt"> &
|
|
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',
|
|
])
|
|
})
|
|
})
|