Files
kite/frontend/tests/DocumentCard.spec.js
curo1305 c45d9e470d feat(07-05): DocumentCard classification_failed badge + Re-analyze button + Vitest coverage
- Add red "Classification failed" pill badge when doc.status === 'classification_failed'
- Re-analyze button calls classifyDocument(doc.id) via @click.stop, emits 'reclassified'
- Spinner text "Re-analyzing…" while in flight; resets after 500ms (via setTimeout)
- Existing is_shared badge block preserved unchanged
- Create frontend/tests/DocumentCard.spec.js: 4 Vitest tests covering badge render,
  no-badge for ready/processing, and reanalyze() calling classifyDocument + emit
- 131 frontend tests pass, build exits 0 (D-11 frontend coverage per CLAUDE.md)
2026-06-04 23:23:12 +02:00

123 lines
4.7 KiB
JavaScript

/**
* 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: '<span></span>' },
}))
vi.mock('../src/components/sharing/ShareModal.vue', () => ({
default: { template: '<div></div>' },
}))
// ── 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'])
})
})