From ec5fd23ec21130ddabeb07badc0a4addce83e025 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 15 Jun 2026 20:22:34 +0200 Subject: [PATCH 1/4] =?UTF-8?q?test(10-08):=20promote=20admin=20skeleton?= =?UTF-8?q?=20stubs=20to=20real=20RED=20tests=20=E2=80=94=208=20failing=20?= =?UTF-8?q?tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdminAuditView.skeleton.test.js: 5 real assertions (skeleton rows, cell count, no loading text, EmptyState, CTA) - AdminUsersView.skeleton.test.js: 3 real assertions (skeleton rows, 6 cells, no loading text) - All 8 tests fail until Task 2 updates the view components --- .../__tests__/AdminAuditView.skeleton.test.js | 87 +++++++++++++++++-- .../__tests__/AdminUsersView.skeleton.test.js | 61 ++++++++++++- 2 files changed, 138 insertions(+), 10 deletions(-) diff --git a/frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js b/frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js index 242bd27..1adbec8 100644 --- a/frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js +++ b/frontend/src/views/admin/__tests__/AdminAuditView.skeleton.test.js @@ -1,12 +1,87 @@ -import { describe, it } from 'vitest' +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', () => { - it.todo('renders 5+ skeleton rows when loading=true') - it.todo('skeleton rows have 5 columns matching the real table header') - it.todo('animated spinner Loading audit log… text is removed') + 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', () => { - it.todo('EmptyState icon=clipboardList headline="No entries found" appears when entries.length===0 and not loading') - it.todo('clear-filters button rendered in #cta slot') + 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 }) + const wrapper = await mountAudit() + await new Promise(r => setTimeout(r, 50)) + await wrapper.vm.$nextTick() + expect(wrapper.text()).toContain('Clear filters') + }) }) diff --git a/frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js b/frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js index 584951d..b66bc54 100644 --- a/frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js +++ b/frontend/src/views/admin/__tests__/AdminUsersView.skeleton.test.js @@ -1,7 +1,60 @@ -import { describe, it } from 'vitest' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { mount } from '@vue/test-utils' +import { setActivePinia, createPinia } from 'pinia' + +vi.mock('../../../api/client.js', () => ({ + adminListUsers: vi.fn().mockResolvedValue({ items: [] }), + adminCreateUser: vi.fn().mockResolvedValue({}), + adminDeactivateUser: vi.fn().mockResolvedValue({}), + adminReactivateUser: vi.fn().mockResolvedValue({}), + adminDeleteUser: vi.fn().mockResolvedValue({}), + adminResetUserPassword: vi.fn().mockResolvedValue({}), +})) + +const stubs = { + BreadcrumbBar: true, + EmptyState: true, + AppIcon: true, +} + +async function mountUsers(overrides = {}) { + const { default: AdminUsersView } = await import('../AdminUsersView.vue') + return mount(AdminUsersView, { + global: { stubs, plugins: [createPinia()] }, + ...overrides, + }) +} describe('UX-04: AdminUsersView shows skeleton table rows during loading', () => { - it.todo('renders 5+ skeleton rows when loading=true') - it.todo('skeleton rows have 6 columns matching the real table header') - it.todo('animated spinner Loading users… text is removed') + beforeEach(() => { + setActivePinia(createPinia()) + vi.resetModules() + }) + + it('renders 5 skeleton rows when loading=true', async () => { + const { adminListUsers } = await import('../../../api/client.js') + adminListUsers.mockReturnValue(new Promise(() => {})) // never resolves => loading stays true + const wrapper = await mountUsers() + const tbody = wrapper.find('tbody') + expect(tbody.exists()).toBe(true) + const rows = tbody.findAll('tr') + expect(rows.length).toBeGreaterThanOrEqual(5) + }) + + it('skeleton rows have exactly 6 cells', async () => { + const { adminListUsers } = await import('../../../api/client.js') + adminListUsers.mockReturnValue(new Promise(() => {})) + const wrapper = await mountUsers() + const rows = wrapper.find('tbody').findAll('tr') + rows.forEach(row => { + expect(row.findAll('td').length).toBe(6) + }) + }) + + it('Loading users text is absent when loading=true', async () => { + const { adminListUsers } = await import('../../../api/client.js') + adminListUsers.mockReturnValue(new Promise(() => {})) + const wrapper = await mountUsers() + expect(wrapper.text()).not.toContain('Loading users') + }) }) From 8e360f4f2142adfeaa428ee4a96872691842561d Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 15 Jun 2026 20:25:05 +0200 Subject: [PATCH 2/4] feat(10-08): skeleton tables + BreadcrumbBar + EmptyState in AdminAuditView and AdminUsersView - 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 --- frontend/src/views/admin/AdminAuditView.vue | 81 +++++++++++-------- frontend/src/views/admin/AdminUsersView.vue | 35 +++++--- .../__tests__/AdminAuditView.skeleton.test.js | 6 +- 3 files changed, 74 insertions(+), 48 deletions(-) diff --git a/frontend/src/views/admin/AdminAuditView.vue b/frontend/src/views/admin/AdminAuditView.vue index b50c260..c80a8e6 100644 --- a/frontend/src/views/admin/AdminAuditView.vue +++ b/frontend/src/views/admin/AdminAuditView.vue @@ -1,5 +1,7 @@