test(web): cover workspace UI branches
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import type {
|
||||
SessionId, SessionListState, SessionSummary, WorkspaceId, WorkspaceView,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { deriveGroups, formatRelativeTime, UNGROUPED_KEY } from '../src/client/tree.ts'
|
||||
import { deriveGroups, formatRelativeTime, projectLabel, UNGROUPED_KEY, UNGROUPED_LABEL } from '../src/client/tree.ts'
|
||||
|
||||
const sid = (id: string) => id as SessionId
|
||||
const wid = (id: string) => id as WorkspaceId
|
||||
@@ -59,6 +59,90 @@ describe('deriveGroups', () => {
|
||||
expect(groups[0]!.intentHere).toBe(false)
|
||||
expect(groups[0]!.sessionCount).toBe(2)
|
||||
})
|
||||
|
||||
it('builds, sorts, expands, and cycle-guards an ungrouped session tree', () => {
|
||||
const parent = summary('parent', 1)
|
||||
const oldChild = { ...summary('old-child', 10), parentId: parent.id }
|
||||
const newChild = { ...summary('new-child', 20), parentId: parent.id }
|
||||
const tieB = { ...summary('tie-b', 20), parentId: parent.id }
|
||||
const tieA = { ...summary('tie-a', 20), parentId: parent.id }
|
||||
const self = { ...summary('self', 2), parentId: sid('self') }
|
||||
const orphan = { ...summary('orphan', 3), parentId: sid('missing') }
|
||||
const cycleA = { ...summary('cycle-a', 4), parentId: sid('cycle-b') }
|
||||
const cycleB = { ...summary('cycle-b', 5), parentId: sid('cycle-a') }
|
||||
const groups = deriveGroups(
|
||||
list(parent, oldChild, newChild, tieB, tieA, self, orphan, cycleA, cycleB),
|
||||
[],
|
||||
{ expandedProjects: [UNGROUPED_KEY], expandedSessions: [parent.id, cycleA.id, cycleB.id], query: '' },
|
||||
)
|
||||
|
||||
expect(groups).toHaveLength(1)
|
||||
expect(groups[0]!.sessions.map(node => node.id)).toEqual([
|
||||
sid('orphan'), sid('self'), parent.id, sid('cycle-a'),
|
||||
])
|
||||
expect(groups[0]!.sessions[2]!.children.map(node => node.id)).toEqual([
|
||||
newChild.id, tieA.id, tieB.id, oldChild.id,
|
||||
])
|
||||
|
||||
// Equal timestamps use ids as a deterministic tiebreak in either input order.
|
||||
expect(deriveGroups(list(summary('tie-a', 1), summary('tie-b', 1)), [], view([UNGROUPED_KEY]))[0]!
|
||||
.sessions.map(node => node.id)).toEqual([sid('tie-a'), sid('tie-b')])
|
||||
})
|
||||
|
||||
it('tolerates Workspace membership arriving before its Session summary', () => {
|
||||
const partial: SessionListState = {
|
||||
...list(),
|
||||
ids: [sid('present')],
|
||||
byId: { [sid('present')]: summary('present', 1) },
|
||||
}
|
||||
const groups = deriveGroups(partial, [workspace('project', ['missing', 'present'])], view(['project']))
|
||||
expect(groups[0]!.sessions.map(node => node.id)).toEqual([sid('present')])
|
||||
})
|
||||
|
||||
it('searches descendants with ancestors and handles cycles, self parents, and label-only hits', () => {
|
||||
const root = { ...summary('root', 1), displayTitle: 'Ancestor' }
|
||||
const match = { ...summary('match', 2), displayTitle: 'Needle child', parentId: root.id }
|
||||
const sibling = { ...summary('sibling', 3), displayTitle: 'Other child', parentId: root.id }
|
||||
const self = { ...summary('self', 4), displayTitle: 'Needle self', parentId: sid('self') }
|
||||
const orphan = { ...summary('orphan', 5), displayTitle: 'Needle orphan', parentId: sid('absent') }
|
||||
const cycleA = { ...summary('cycle-a', 6), displayTitle: 'Needle cycle A', parentId: sid('cycle-b') }
|
||||
const cycleB = { ...summary('cycle-b', 7), displayTitle: 'Needle cycle B', parentId: sid('cycle-a') }
|
||||
const sessions = list(root, match, sibling, self, orphan, cycleA, cycleB)
|
||||
const groups = deriveGroups(sessions, [workspace('project', sessions.ids)], view([], 'needle'))
|
||||
|
||||
expect(groups[0]!.sessions.flatMap(node => [node.id, ...node.children.map(child => child.id)])).toEqual([
|
||||
root.id, match.id, self.id, orphan.id, cycleA.id, cycleB.id,
|
||||
])
|
||||
|
||||
const labelOnly = deriveGroups(
|
||||
list(summary('hidden', 1)),
|
||||
[workspace('label-hit', ['hidden']), workspace('other', [])],
|
||||
view([], 'label'),
|
||||
)
|
||||
expect(labelOnly).toEqual([
|
||||
expect.objectContaining({ key: 'label-hit', expanded: false, sessions: [], sessionCount: 1 }),
|
||||
])
|
||||
})
|
||||
|
||||
it('marks selected Workspace and Ungrouped sessions without relying on an Intent', () => {
|
||||
const owned = summary('owned', 1)
|
||||
const loose = summary('loose', 2)
|
||||
const ws = workspace('project', ['owned'])
|
||||
const ownedGroups = deriveGroups({ ...list(owned, loose), current: owned.id }, [ws], view())
|
||||
expect(ownedGroups.find(group => group.key === 'project')!.containsCurrent).toBe(true)
|
||||
const looseGroups = deriveGroups({ ...list(owned, loose), current: loose.id }, [ws], view())
|
||||
expect(looseGroups.find(group => group.key === UNGROUPED_KEY)!.containsCurrent).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('projectLabel', () => {
|
||||
it('uses the Ungrouped fallback and extracts POSIX and Windows basenames', () => {
|
||||
expect(projectLabel(undefined)).toBe(UNGROUPED_LABEL)
|
||||
expect(projectLabel('')).toBe(UNGROUPED_LABEL)
|
||||
expect(projectLabel('/projects/demo/')).toBe('demo')
|
||||
expect(projectLabel('C:\\projects\\demo\\')).toBe('demo')
|
||||
expect(projectLabel('/')).toBe('/')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatRelativeTime', () => {
|
||||
|
||||
Reference in New Issue
Block a user