mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
* fix(web): keep pin dividers coherent under search relevance sort Address tiann's #1842 review: preserve project-pinned sections when ranking, drop ineffective single-term IDF, add title phrase bonus. Co-authored-by: Cursor <cursoragent@cursor.com> * test(web): add component regression for search pin dividers Render tiann's four-session repro under query "home" and assert the session list draws at most one project pin divider. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
4.8 KiB
TypeScript
127 lines
4.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { SessionSummary } from '@/types/api'
|
|
import { DEFAULT_SESSION_PREVIEW_LIMIT } from '@/hooks/useSessionPreviewLimit'
|
|
import {
|
|
countHiddenActiveSharePickerSessions,
|
|
filterSharePickerSessions,
|
|
} from './sharePickerSessions'
|
|
|
|
function makeSession(overrides: Partial<SessionSummary> & { id: string }): SessionSummary {
|
|
return {
|
|
active: false,
|
|
thinking: false,
|
|
activeAt: 0,
|
|
updatedAt: 0,
|
|
metadata: null,
|
|
metadataVersion: 0,
|
|
agentStateVersion: 0,
|
|
todosUpdatedAt: 0,
|
|
todoProgress: null,
|
|
pendingRequestsCount: 0,
|
|
pendingRequestKinds: [],
|
|
pendingRequests: [],
|
|
backgroundTaskCount: 0,
|
|
futureScheduledMessageCount: 0,
|
|
nextScheduledAt: null,
|
|
model: null,
|
|
effort: null,
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
const machineLabel = () => 'desktop'
|
|
|
|
describe('filterSharePickerSessions', () => {
|
|
it('returns active sessions sorted by updatedAt when query is empty', () => {
|
|
const sessions = [
|
|
makeSession({ id: 'old-active', active: true, updatedAt: 100 }),
|
|
makeSession({ id: 'inactive-recent', active: false, updatedAt: 300 }),
|
|
makeSession({ id: 'new-active', active: true, updatedAt: 200 }),
|
|
]
|
|
const result = filterSharePickerSessions(sessions, '', machineLabel)
|
|
expect(result.map((s) => s.id)).toEqual(['new-active', 'old-active'])
|
|
})
|
|
|
|
it('caps active sessions when query is empty', () => {
|
|
const previewLimit = DEFAULT_SESSION_PREVIEW_LIMIT
|
|
const sessions = Array.from({ length: previewLimit + 3 }, (_, index) =>
|
|
makeSession({ id: `s-${index}`, active: true, updatedAt: index }))
|
|
const result = filterSharePickerSessions(sessions, '', machineLabel, null, previewLimit)
|
|
expect(result).toHaveLength(previewLimit)
|
|
expect(result[0].id).toBe(`s-${previewLimit + 2}`)
|
|
})
|
|
|
|
it('searches all sessions including inactive when query is non-empty', () => {
|
|
const sessions = [
|
|
makeSession({
|
|
id: 'inactive-match',
|
|
active: false,
|
|
updatedAt: 50,
|
|
metadata: { path: '/proj/archive', summary: { text: 'Old bugfix' } },
|
|
}),
|
|
makeSession({ id: 'active-no-match', active: true, updatedAt: 200, metadata: { path: '/other' } }),
|
|
]
|
|
const result = filterSharePickerSessions(sessions, 'archive', machineLabel)
|
|
expect(result.map((s) => s.id)).toEqual(['inactive-match'])
|
|
})
|
|
|
|
it('matches machine label in search mode', () => {
|
|
const sessions = [
|
|
makeSession({
|
|
id: 'remote',
|
|
active: false,
|
|
updatedAt: 100,
|
|
metadata: { path: '/proj', machineId: 'machine-abc' },
|
|
}),
|
|
]
|
|
const result = filterSharePickerSessions(
|
|
sessions,
|
|
'laptop',
|
|
(machineId) => (machineId === 'machine-abc' ? 'dev-laptop' : 'unknown'),
|
|
)
|
|
expect(result.map((s) => s.id)).toEqual(['remote'])
|
|
})
|
|
|
|
it('requires every multi-word token (AND) via shared sessionMatchesQuery', () => {
|
|
const sessions = [
|
|
makeSession({
|
|
id: 'full-and',
|
|
active: false,
|
|
updatedAt: 50,
|
|
metadata: { path: '/proj/home', name: 'Assistant draft' },
|
|
}),
|
|
makeSession({
|
|
id: 'home-only',
|
|
active: true,
|
|
updatedAt: 200,
|
|
metadata: { path: '/proj/home', name: 'other' },
|
|
}),
|
|
]
|
|
const result = filterSharePickerSessions(sessions, 'home assistant', machineLabel)
|
|
expect(result.map((s) => s.id)).toEqual(['full-and'])
|
|
})
|
|
})
|
|
describe('countHiddenActiveSharePickerSessions', () => {
|
|
it('returns zero when active count is within cap', () => {
|
|
const sessions = [
|
|
makeSession({ id: 'a', active: true }),
|
|
makeSession({ id: 'b', active: false }),
|
|
]
|
|
expect(countHiddenActiveSharePickerSessions(sessions)).toBe(0)
|
|
})
|
|
|
|
it('counts active sessions beyond the cap', () => {
|
|
const previewLimit = DEFAULT_SESSION_PREVIEW_LIMIT
|
|
const sessions = Array.from({ length: previewLimit + 2 }, (_, index) =>
|
|
makeSession({ id: `s-${index}`, active: true }))
|
|
expect(countHiddenActiveSharePickerSessions(sessions, previewLimit)).toBe(2)
|
|
})
|
|
|
|
it('honors a custom preview limit', () => {
|
|
const sessions = Array.from({ length: 5 }, (_, index) =>
|
|
makeSession({ id: `s-${index}`, active: true, updatedAt: index }))
|
|
expect(filterSharePickerSessions(sessions, '', machineLabel, null, 3)).toHaveLength(3)
|
|
expect(countHiddenActiveSharePickerSessions(sessions, 3)).toBe(2)
|
|
})
|
|
})
|