/** * Vitest unit tests for DocumentCard.vue — classification-failed badge * and reanalyze() handler. * * D-11 frontend coverage — CLAUDE.md mandate: every new component / function * must have at least one test. */ import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' // ── Mock API client ─────────────────────────────────────────────────────────── vi.mock('../src/api/client.js', () => ({ classifyDocument: vi.fn(() => Promise.resolve({ document_id: 'd1', status: 'processing' }) ), // Stub other imports so the component can load without Pinia moveDocument: vi.fn(() => Promise.resolve()), })) // ── Mock Pinia stores referenced by DocumentCard ────────────────────────────── vi.mock('../src/stores/topics.js', () => ({ useTopicsStore: () => ({ topics: [] }), })) vi.mock('../src/stores/folders.js', () => ({ useFoldersStore: () => ({ rootFolders: [] }), })) // ── Mock child components ───────────────────────────────────────────────────── vi.mock('../src/components/topics/TopicBadge.vue', () => ({ default: { template: '' }, })) vi.mock('../src/components/sharing/ShareModal.vue', () => ({ default: { template: '
' }, })) // ── Mock formatters (used in template) ─────────────────────────────────────── vi.mock('../src/utils/formatters.js', () => ({ formatDate: (v) => String(v), formatSize: (v) => String(v), })) // ── Import component and mock after all mocks are registered ───────────────── import DocumentCard from '../src/components/documents/DocumentCard.vue' import { classifyDocument } from '../src/api/client.js' // ── Minimal doc fixture ─────────────────────────────────────────────────────── function makeDoc(overrides = {}) { return { id: 'd1', original_name: 'test.pdf', size_bytes: 1024, created_at: '2024-01-01T00:00:00Z', topics: [], is_shared: false, status: 'ready', ...overrides, } } // ── Tests ───────────────────────────────────────────────────────────────────── describe('DocumentCard — classification_failed badge', () => { beforeEach(() => { vi.clearAllMocks() }) it('renders Classification failed badge when status === "classification_failed"', () => { const wrapper = mount(DocumentCard, { props: { doc: makeDoc({ status: 'classification_failed' }) }, global: { stubs: { RouterLink: true } }, }) expect(wrapper.text()).toContain('Classification failed') const btn = wrapper.find('button[disabled]') || wrapper.findAll('button').find(b => b.text().includes('Re-analyze')) expect(btn || wrapper.text()).toBeTruthy() expect(wrapper.text()).toContain('Re-analyze') }) it('does NOT render the badge when status is "ready"', () => { const wrapper = mount(DocumentCard, { props: { doc: makeDoc({ status: 'ready' }) }, global: { stubs: { RouterLink: true } }, }) expect(wrapper.text()).not.toContain('Classification failed') expect(wrapper.text()).not.toContain('Re-analyze') }) it('does NOT render the badge when status is "processing"', () => { const wrapper = mount(DocumentCard, { props: { doc: makeDoc({ status: 'processing' }) }, global: { stubs: { RouterLink: true } }, }) expect(wrapper.text()).not.toContain('Classification failed') }) it('Re-analyze button calls classifyDocument(doc.id) and emits "reclassified"', async () => { const wrapper = mount(DocumentCard, { props: { doc: makeDoc({ status: 'classification_failed' }) }, global: { stubs: { RouterLink: true } }, }) // Find the Re-analyze button const buttons = wrapper.findAll('button') const reanalyzeBtn = buttons.find(b => b.text().includes('Re-analyze')) expect(reanalyzeBtn).toBeTruthy() await reanalyzeBtn.trigger('click') await flushPromises() expect(classifyDocument).toHaveBeenCalledOnce() expect(classifyDocument).toHaveBeenCalledWith('d1') const emitted = wrapper.emitted('reclassified') expect(emitted).toBeTruthy() expect(emitted[0]).toEqual(['d1']) }) })