- AdminAuditView: add BreadcrumbBar (Audit Log, showRoot=false), replace loading spinner with 8-row skeleton tbody, replace empty div with EmptyState (icon=clipboardList) + Clear filters CTA - AdminUsersView: add BreadcrumbBar (Users, showRoot=false), replace loading spinner with 5-row x 6-col skeleton tbody - Promote CTA test to use real EmptyState (no stub) so slot content renders - All 8 skeleton tests pass GREEN
92 lines
3.5 KiB
JavaScript
92 lines
3.5 KiB
JavaScript
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 <tr> 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 <td> 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 <EmptyState icon="clipboardList" headline="No entries found"> 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')
|
|
})
|
|
})
|