/** * 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 { 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() }) })