112 lines
No EOL
3.8 KiB
TypeScript
112 lines
No EOL
3.8 KiB
TypeScript
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<typeof vi.fn>).mockResolvedValue(apps)
|
|
;(api.batchScore as ReturnType<typeof vi.fn>).mockResolvedValue({ results: [] })
|
|
;(api.getToday as ReturnType<typeof vi.fn>).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<typeof vi.fn>).mockResolvedValue(apps)
|
|
;(api.batchScore as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
results: [
|
|
{
|
|
application_id: 'app-1',
|
|
score: 30,
|
|
rationale: {},
|
|
red_flags: ['Unpaid trial period mentioned', 'Asks for bank details upfront']
|
|
}
|
|
]
|
|
})
|
|
;(api.getToday as ReturnType<typeof vi.fn>).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')
|
|
})
|
|
}) |