import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount } from '@vue/test-utils' import { setActivePinia, createPinia } from 'pinia' vi.mock('../../../api/client.js', () => ({ adminListAuditLog: vi.fn().mockResolvedValue({ items: [], total: 0 }), adminExportAuditLogCsv: vi.fn().mockResolvedValue({}), adminListDailyExports: vi.fn().mockResolvedValue({ items: [] }), adminDownloadDailyExport: vi.fn().mockResolvedValue({}), })) const stubs = { BreadcrumbBar: true, EmptyState: true, AppIcon: true, } async function mountAudit(overrides = {}) { const { default: AdminAuditView } = await import('../AdminAuditView.vue') return mount(AdminAuditView, { global: { stubs, plugins: [createPinia()] }, ...overrides, }) } describe('UX-04: AdminAuditView shows skeleton table rows during loading', () => { beforeEach(() => { setActivePinia(createPinia()) vi.resetModules() }) it('renders 8 skeleton rows when loading=true', async () => { const { adminListAuditLog } = await import('../../../api/client.js') adminListAuditLog.mockReturnValue(new Promise(() => {})) // never resolves => loading stays true const wrapper = await mountAudit() const tbody = wrapper.find('tbody') expect(tbody.exists()).toBe(true) const rows = tbody.findAll('tr') expect(rows.length).toBeGreaterThanOrEqual(8) }) it('skeleton rows have exactly 5 cells matching column count', async () => { const { adminListAuditLog } = await import('../../../api/client.js') adminListAuditLog.mockReturnValue(new Promise(() => {})) const wrapper = await mountAudit() const rows = wrapper.find('tbody').findAll('tr') rows.forEach(row => { expect(row.findAll('td').length).toBe(5) }) }) it('Loading audit log text is absent when loading=true', async () => { const { adminListAuditLog } = await import('../../../api/client.js') adminListAuditLog.mockReturnValue(new Promise(() => {})) const wrapper = await mountAudit() expect(wrapper.text()).not.toContain('Loading audit log') }) }) describe('UX-01 (audit empty): EmptyState renders when entries is empty after load', () => { beforeEach(() => { setActivePinia(createPinia()) vi.resetModules() }) it('renders when entries empty and not loading', async () => { const { adminListAuditLog } = await import('../../../api/client.js') adminListAuditLog.mockResolvedValue({ items: [], total: 0 }) const wrapper = await mountAudit() await new Promise(r => setTimeout(r, 50)) await wrapper.vm.$nextTick() const emptyState = wrapper.findComponent({ name: 'EmptyState' }) expect(emptyState.exists()).toBe(true) expect(emptyState.attributes('icon') ?? emptyState.props('icon')).toBe('clipboardList') const headline = emptyState.attributes('headline') ?? emptyState.props('headline') expect(headline).toBe('No entries found') }) it('EmptyState includes a Clear filters CTA button', async () => { const { adminListAuditLog } = await import('../../../api/client.js') adminListAuditLog.mockResolvedValue({ items: [], total: 0 }) // Mount with EmptyState NOT stubbed so slot content renders const { default: AdminAuditView } = await import('../AdminAuditView.vue') const wrapper = mount(AdminAuditView, { global: { stubs: { BreadcrumbBar: true, AppIcon: true }, plugins: [createPinia()] }, }) await new Promise(r => setTimeout(r, 50)) await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('Clear filters') }) })