fischer-agentkit/src/agentkit/server/frontend/tests/unit/components/AutonomyPausedCard.test.ts

151 lines
4.6 KiB
TypeScript

/**
* IQ-Boost/U1: unit tests for AutonomyPausedCard.
*
* Mount strategy: native Vue createApp + h (no @vue/test-utils dependency),
* consistent with BoardBannerCard.test.ts. Verifies:
* - reason → 中文标签映射 (timeout / consecutive_failures / manual)
* - progress 摘要渲染 (步骤 N / M · 工具 X)
* - resume 按钮点击 emit resume 事件携带 resume_token
*/
import { afterEach, describe, expect, it } from 'vitest'
import { createApp, h, type App } from 'vue'
import AutonomyPausedCard from '@/components/chat/messages/AutonomyPausedCard.vue'
import type { IAutonomyPausedData } from '@/api/types'
interface Mounted {
container: HTMLElement
root: HTMLElement
app: App
unmount: () => void
}
function mountCard(
props: { data: IAutonomyPausedData },
onResume?: (token: string) => void,
): Mounted {
const container = document.createElement('div')
document.body.appendChild(container)
const app = createApp({
setup() {
return () =>
h(AutonomyPausedCard as never, {
data: props.data as never,
onResume: (token: string) => onResume?.(token),
} as never)
},
})
app.mount(container)
const root = container.querySelector('.autonomy-card') as HTMLElement
return {
container,
root,
app,
unmount: () => {
app.unmount()
container.remove()
},
}
}
function makeData(
overrides: Partial<IAutonomyPausedData> = {},
): IAutonomyPausedData {
return {
reason: 'timeout',
progress: { step: 3, tool_name: 'shell', total_steps: 10 },
resume_token: 'autonomy_pause:timeout:3',
consecutive_failures: 0,
elapsed_seconds: 305.4,
...overrides,
}
}
describe('AutonomyPausedCard (IQ-Boost/U1)', () => {
let mounted: Mounted | null = null
afterEach(() => {
mounted?.unmount()
mounted = null
})
it('renders reason=timeout as "自主执行超时"', () => {
mounted = mountCard({ data: makeData({ reason: 'timeout' }) })
const reason = mounted.container.querySelector(
'.autonomy-card__reason',
) as HTMLElement
expect(reason).toBeTruthy()
expect(reason.textContent).toBe('自主执行超时')
})
it('renders reason=consecutive_failures as "连续失败触发暂停"', () => {
mounted = mountCard({
data: makeData({ reason: 'consecutive_failures', consecutive_failures: 3 }),
})
const reason = mounted.container.querySelector(
'.autonomy-card__reason',
) as HTMLElement
expect(reason.textContent).toBe('连续失败触发暂停')
// consecutive_failures count is also shown in meta.
const meta = mounted.container.querySelector(
'.autonomy-card__meta',
) as HTMLElement
expect(meta.textContent).toContain('连续失败 3 次')
})
it('renders reason=manual as "用户手动暂停"', () => {
mounted = mountCard({ data: makeData({ reason: 'manual' }) })
const reason = mounted.container.querySelector(
'.autonomy-card__reason',
) as HTMLElement
expect(reason.textContent).toBe('用户手动暂停')
})
it('renders progress summary "步骤 3 / 10 · 工具 shell"', () => {
mounted = mountCard({ data: makeData() })
const progress = mounted.container.querySelector(
'.autonomy-card__progress',
) as HTMLElement
expect(progress).toBeTruthy()
expect(progress.textContent).toContain('步骤 3 / 10')
expect(progress.textContent).toContain('工具 shell')
})
it('renders elapsed time "已运行 5m 5s" for 305.4s', () => {
mounted = mountCard({ data: makeData({ elapsed_seconds: 305.4 }) })
const meta = mounted.container.querySelector(
'.autonomy-card__meta',
) as HTMLElement
expect(meta.textContent).toContain('已运行 5m 5s')
})
it('emits resume with resume_token when 继续 button is clicked', async () => {
let emittedToken: string | null = null
mounted = mountCard(
{ data: makeData({ resume_token: 'autonomy_pause:timeout:7' }) },
(token) => {
emittedToken = token
},
)
const button = mounted.container.querySelector(
'.autonomy-card__actions button',
) as HTMLButtonElement
expect(button).toBeTruthy()
button.click()
// Vue event flush — await a microtask.
await Promise.resolve()
expect(emittedToken).toBe('autonomy_pause:timeout:7')
})
it('hides progress block when progress has no known fields', () => {
mounted = mountCard({
data: makeData({ progress: {} }),
})
const progress = mounted.container.querySelector(
'.autonomy-card__progress',
)
// v-if="progressSummary" — empty progress → no render.
expect(progress).toBeNull()
})
})