avocet/web/src/views/LabelView.test.ts

92 lines
3.1 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import LabelView from './LabelView.vue'
import EmailCardStack from '../components/EmailCardStack.vue'
import { describe, it, expect, vi, beforeEach } from 'vitest'
// Mock fetch globally
beforeEach(() => {
setActivePinia(createPinia())
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ items: [], total: 0 }),
text: async () => '',
}))
})
describe('LabelView', () => {
it('shows loading state initially', () => {
const w = mount(LabelView, {
global: { plugins: [createPinia()] },
})
// Should show skeleton while loading
expect(w.find('.skeleton-card').exists()).toBe(true)
})
it('shows empty state when queue is empty after load', async () => {
const w = mount(LabelView, {
global: { plugins: [createPinia()] },
})
// Let all promises resolve
await new Promise(r => setTimeout(r, 0))
await w.vm.$nextTick()
expect(w.find('.empty-state').exists()).toBe(true)
})
it('renders header with action buttons', async () => {
const w = mount(LabelView, {
global: { plugins: [createPinia()] },
})
await new Promise(r => setTimeout(r, 0))
await w.vm.$nextTick()
expect(w.find('.lv-header').exists()).toBe(true)
expect(w.text()).toContain('Undo')
expect(w.text()).toContain('Skip')
expect(w.text()).toContain('Discard')
})
const queueItem = {
id: 'test-1', subject: 'Test Email', body: 'Test body',
from: 'test@test.com', date: '2026-03-05', source: 'test',
}
// Return queue items for /api/queue, empty array for /api/config/labels
function mockFetchWithQueue() {
vi.stubGlobal('fetch', vi.fn().mockImplementation((url: string) =>
Promise.resolve({
ok: true,
json: async () => (url as string).includes('/api/queue')
? { items: [queueItem], total: 1 }
: [],
text: async () => '',
})
))
}
it('renders toss zone overlays when isHeld is true (after drag-start)', async () => {
mockFetchWithQueue()
const w = mount(LabelView, { global: { plugins: [createPinia()] } })
await new Promise(r => setTimeout(r, 0))
await w.vm.$nextTick()
// Zone overlays should not exist before drag
expect(w.find('.toss-zone-left').exists()).toBe(false)
// Emit drag-start from EmailCardStack child
const cardStack = w.findComponent(EmailCardStack)
cardStack.vm.$emit('drag-start')
await w.vm.$nextTick()
expect(w.find('.toss-zone-left').exists()).toBe(true)
expect(w.find('.toss-zone-right').exists()).toBe(true)
})
it('bucket-grid-footer has grid-active class while card is held', async () => {
mockFetchWithQueue()
const w = mount(LabelView, { global: { plugins: [createPinia()] } })
await new Promise(r => setTimeout(r, 0))
await w.vm.$nextTick()
expect(w.find('.bucket-grid-footer').classes()).not.toContain('grid-active')
const cardStack = w.findComponent(EmailCardStack)
cardStack.vm.$emit('drag-start')
await w.vm.$nextTick()
expect(w.find('.bucket-grid-footer').classes()).toContain('grid-active')
})
})