137 lines
No EOL
4.4 KiB
TypeScript
137 lines
No EOL
4.4 KiB
TypeScript
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<typeof vi.fn>).mockResolvedValue([app])
|
|
;(api.getArtifacts as ReturnType<typeof vi.fn>).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<typeof vi.fn>).mockResolvedValue(makeTailorResult())
|
|
// After tailoring, getArtifacts is called again to refresh
|
|
;(api.getArtifacts as ReturnType<typeof vi.fn>).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')
|
|
})
|
|
}) |