import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import Applications from '@/views/Applications.vue' import type { Application } from '@/types' vi.mock('@/api', () => ({ getApplications: vi.fn(), transitionApplication: vi.fn(), batchScore: vi.fn(), getToday: 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(id: string, state: string, company: string): Application { return { id, job_posting_id: 'j-' + id, state: state as Application['state'], score: 80, score_rationale: null, notes: '', state_changed_at: '2026-01-01T00:00:00Z', created_at: '2026-01-01T00:00:00Z', posting: { id: 'j-' + id, source: 'manual_url', external_id: null, url: 'http://x', company, title: 'Engineer', location: 'Remote', description: '', raw: {}, fetched_at: '2026-01-01T00:00:00Z' } } } describe('Applications kanban badges', () => { it('renders nudge dot on cards that have a follow-up nudge', async () => { setActivePinia(createPinia()) const api = await import('@/api') const apps = [makeApp('app-1', 'sent', 'NudgeCorp'), makeApp('app-2', 'discovered', 'NoNudge')] ;(api.getApplications as ReturnType).mockResolvedValue(apps) ;(api.batchScore as ReturnType).mockResolvedValue({ results: [] }) ;(api.getToday as ReturnType).mockResolvedValue({ digest: [], nudges: [ { application_id: 'app-1', days_since_sent: 9, suggestion: 'Send a follow-up email' } ], pending_approvals: 0 }) const wrapper = mount(Applications) await vi.waitFor(() => { expect(wrapper.text()).toContain('NudgeCorp') }) // The nudge dot should be rendered as an orange dot (span with bg-orange-500) const dots = wrapper.findAll('.bg-orange-500') expect(dots.length).toBeGreaterThanOrEqual(1) // Verify the dot is in the card for NudgeCorp (the sent column) const sentCol = wrapper.findAll('.font-semibold').find((el) => el.text() === 'sent') expect(sentCol).toBeTruthy() const sentColumn = sentCol!.element.parentElement! expect(sentColumn.textContent).toContain('NudgeCorp') // The dot should be inside this column expect(sentColumn.querySelector('.bg-orange-500')).toBeTruthy() }) it('renders red flag badge with tooltip text from batch scoring results', async () => { setActivePinia(createPinia()) const api = await import('@/api') const apps = [makeApp('app-1', 'discovered', 'ScamCorp')] ;(api.getApplications as ReturnType).mockResolvedValue(apps) ;(api.batchScore as ReturnType).mockResolvedValue({ results: [ { application_id: 'app-1', score: 30, rationale: {}, red_flags: ['Unpaid trial period mentioned', 'Asks for bank details upfront'] } ] }) ;(api.getToday as ReturnType).mockResolvedValue({ digest: [], nudges: [], pending_approvals: 0 }) const wrapper = mount(Applications) await vi.waitFor(() => { expect(wrapper.text()).toContain('ScamCorp') }) // The warning symbol should be rendered in the card const warningEl = wrapper.find('.text-red-600.font-bold') expect(warningEl.exists()).toBe(true) // The title attribute should contain the red flag text const title = warningEl.attributes('title') expect(title).toBeTruthy() expect(title).toContain('Unpaid trial period mentioned') expect(title).toContain('Asks for bank details upfront') }) })