import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import type { Application, Artifact, TailorCvResponse } from '@/types' vi.mock('@/api', () => ({ getApplications: vi.fn(), getArtifacts: vi.fn(), createCoverLetter: vi.fn(), createApproval: vi.fn(), confirmApproval: vi.fn(), rejectApproval: vi.fn(), outboxSend: vi.fn(), interviewPrep: vi.fn(), tailorCv: vi.fn(), HttpError: class HttpError extends Error { status: number body: unknown constructor(status: number, body: unknown, msg?: string) { super(msg ?? `HTTP ${status}`) this.status = status this.body = body } } })) function makeApp(): Application { return { id: 'app-1', job_posting_id: 'j-1', state: 'drafting', score: 90, score_rationale: null, notes: '', state_changed_at: '2026-01-01T00:00:00Z', created_at: '2026-01-01T00:00:00Z', posting: { id: 'j-1', source: 'manual_url', external_id: null, url: 'http://x', company: 'Acme', title: 'Engineer', location: 'Remote', description: '', raw: {}, fetched_at: '2026-01-01T00:00:00Z' } } } function makeArtifact(): Artifact { return { id: 'art-1', application_id: 'app-1', kind: 'cover_letter', filename: 'cover.pdf', content_hash: 'abcdef0123456789', storage_path: '/tmp/cover.pdf', version: 1, origin: 'user_drafted', created_at: '2026-01-01T00:00:00Z' } } function makeTailorResult(): TailorCvResponse { return { artifact_id: 'art-tailor-1', change_log: [ { section: 'experience', change: 'Reordered to highlight Python backend work', bullet_id: 'b-1' }, { section: 'skills', change: 'Moved Docker and Kubernetes higher', bullet_id: 'b-5' } ], keyword_coverage: 0.75 } } async function mountDetail(app: Application, artifacts: Artifact[]) { setActivePinia(createPinia()) const api = await import('@/api') ;(api.getApplications as ReturnType).mockResolvedValue([app]) ;(api.getArtifacts as ReturnType).mockResolvedValue(artifacts) const ApplicationDetail = (await import('@/views/ApplicationDetail.vue')).default const wrapper = mount(ApplicationDetail, { props: { id: 'app-1' } }) await flushPromises() return { wrapper } } describe('Tailor CV panel', () => { beforeEach(() => { vi.clearAllMocks() }) it('renders change log, coverage bar, and download link after tailoring', async () => { const { wrapper } = await mountDetail(makeApp(), [makeArtifact()]) const api = await import('@/api') // Panel should not be visible before clicking expect(wrapper.find('[data-testid="tailor-panel"]').exists()).toBe(false) // Mock tailorCv to return a result ;(api.tailorCv as ReturnType).mockResolvedValue(makeTailorResult()) // After tailoring, getArtifacts is called again to refresh ;(api.getArtifacts as ReturnType).mockResolvedValue([makeArtifact(), { id: 'art-tailor-1', application_id: 'app-1', kind: 'cv', filename: 'tailored_cv.pdf', content_hash: 'deadbeef01234567', storage_path: '/tmp/tailored_cv.pdf', version: 1, origin: 'ai_drafted', created_at: '2026-07-30T00:00:00Z' }]) // Click the Tailor CV button const btn = wrapper.find('[data-testid="tailor-cv-btn"]') expect(btn.exists()).toBe(true) await btn.trigger('click') await flushPromises() // Panel should now be visible const panel = wrapper.find('[data-testid="tailor-panel"]') expect(panel.exists()).toBe(true) // Change log entries should be visible expect(panel.text()).toContain('Reordered to highlight Python backend work') expect(panel.text()).toContain('Moved Docker and Kubernetes higher') // Coverage bar should be present with 75% expect(panel.text()).toContain('Keyword Coverage') expect(panel.text()).toContain('75%') const bar = panel.find('[data-testid="coverage-bar"]') expect(bar.exists()).toBe(true) expect(bar.attributes('style')).toContain('width: 75%') // Download link should be present const dl = panel.find('[data-testid="download-link"]') expect(dl.exists()).toBe(true) expect(dl.text()).toContain('Download tailored CV') // Tailored artifact should appear in artifacts list expect(wrapper.text()).toContain('tailored_cv.pdf') }) })