import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AdminAnalyticsPage } from './AdminAnalyticsPage'; const { api } = vi.hoisted(() => ({ api: { getSignatureQuality: vi.fn(), getSignatureRetirementHeatmap: vi.fn(), getUnreportedSignatures: vi.fn() }, })); vi.mock('@/api/adminApi', () => ({ adminApi: api })); describe('independent analytics tabs', () => { it('combines separate activity search fields with AND and preserves them across tabs', async () => { api.getSignatureRetirementHeatmap.mockResolvedValue({ items: [], dimensions: [ { dimensionType: 'channel', signatureId: 'a', signatureName: '签名甲', tenantName: '企业甲', applicationName: '应用甲', channelName: '通道甲', channelId: 'c', carrier: 'mobile', approvedAt: '2026-08-01', }, { dimensionType: 'channel', signatureId: 'b', signatureName: '签名乙', tenantName: '企业甲', applicationName: '应用乙', channelName: '通道乙', channelId: 'd', carrier: 'unicom', approvedAt: '2026-08-01', }, ], }); render(); fireEvent.click(screen.getByRole('tab', { name: '通道签名活跃度' })); const panel = screen.getByRole('region', { name: '通道签名活跃度' }); await within(panel).findByText('签名甲'); fireEvent.change(within(panel).getByLabelText('企业'), { target: { value: '企业甲' } }); fireEvent.change(within(panel).getByLabelText('企业应用'), { target: { value: '应用甲' } }); await waitFor(() => expect(within(panel).queryByText('签名乙')).not.toBeInTheDocument()); fireEvent.change(within(panel).getByLabelText('通道'), { target: { value: '通道乙' } }); await waitFor(() => expect(within(panel).queryByText('签名甲')).not.toBeInTheDocument()); fireEvent.click(screen.getByRole('tab', { name: '企业签名活跃度' })); fireEvent.click(screen.getByRole('tab', { name: '通道签名活跃度' })); expect(within(panel).getByLabelText('企业应用')).toHaveValue('应用甲'); }); beforeEach(() => { vi.resetAllMocks(); api.getSignatureQuality.mockImplementation(async (query) => ({ ...query, total: 0, items: [] })); api.getUnreportedSignatures.mockImplementation(async (query) => ({ ...query, total: 0, items: [] })); api.getSignatureRetirementHeatmap.mockResolvedValue({ items: [], dimensions: [] }); }); it('loads only the visited tab and preserves independent dates when switching back', async () => { render(); await waitFor(() => expect(api.getSignatureQuality).toHaveBeenCalledWith(expect.objectContaining({ pageSize: 25 })), ); expect(api.getSignatureRetirementHeatmap).not.toHaveBeenCalled(); const quality = screen.getByRole('region', { name: '签名通道发送质量' }); fireEvent.change(within(quality).getByLabelText('统计日期'), { target: { value: '2026-08-20' } }); fireEvent.click(within(quality).getByRole('button', { name: '查询统计' })); await waitFor(() => expect(api.getSignatureQuality).toHaveBeenLastCalledWith(expect.objectContaining({ date: '2026-08-20' })), ); fireEvent.click(screen.getByRole('tab', { name: '未报备签名' })); await waitFor(() => expect(api.getUnreportedSignatures).toHaveBeenCalledTimes(1)); const unreported = screen.getByRole('region', { name: '未报备签名' }); fireEvent.change(within(unreported).getByLabelText('统计日期'), { target: { value: '2026-08-25' } }); fireEvent.click(screen.getByRole('tab', { name: '签名通道发送质量' })); expect(within(quality).getByLabelText('统计日期')).toHaveValue('2026-08-20'); fireEvent.click(screen.getByRole('tab', { name: '未报备签名' })); expect(within(unreported).getByLabelText('统计日期')).toHaveValue('2026-08-25'); expect(api.getSignatureRetirementHeatmap).not.toHaveBeenCalled(); }); it.each([ ['签名通道发送质量', 'getSignatureQuality'], ['未报备签名', 'getUnreportedSignatures'], ] as const)('keeps %s page size in the footer and resets the page with applied filters', async (tab, method) => { api[method].mockImplementation(async (query) => ({ ...query, total: 120, items: [] })); render(); fireEvent.click(screen.getByRole('tab', { name: tab })); const panel = screen.getByRole('region', { name: tab }); await waitFor(() => expect(within(panel).getByRole('button', { name: '下一页' })).toBeEnabled()); fireEvent.change(within(panel).getByLabelText('统计日期'), { target: { value: '2026-08-20' } }); fireEvent.click(within(panel).getByRole('button', { name: '查询统计' })); await waitFor(() => expect(api[method]).toHaveBeenLastCalledWith(expect.objectContaining({ date: '2026-08-20' }))); fireEvent.click(within(panel).getByRole('button', { name: '下一页' })); await waitFor(() => expect(api[method]).toHaveBeenLastCalledWith(expect.objectContaining({ page: 2 }))); // An unsubmitted date must not alter a pagination request. fireEvent.change(within(panel).getByLabelText('统计日期'), { target: { value: '2026-08-21' } }); const size = within(panel).getByLabelText(/^每页数量/); expect(size.closest('.ui-pagination')).not.toBeNull(); expect(size.closest('.page-actions')).toBeNull(); fireEvent.click(size); expect(screen.getAllByRole('option').map((option) => option.textContent)).toEqual([ '10 条/页', '25 条/页', '50 条/页', '100 条/页', ]); fireEvent.click(screen.getByRole('option', { name: '100 条/页' })); await waitFor(() => expect(api[method]).toHaveBeenLastCalledWith( expect.objectContaining({ page: 1, pageSize: 100, date: '2026-08-20' }), ), ); await waitFor(() => expect(within(panel).getByLabelText('跳转页码')).toHaveValue(1)); }); it.each(['企业签名活跃度', '通道签名活跃度'])( 'preserves %s footer size independently even with no rows', async (tab) => { render(); fireEvent.click(screen.getByRole('tab', { name: tab })); await waitFor(() => expect(api.getSignatureRetirementHeatmap).toHaveBeenCalledTimes(1)); const panel = screen.getByRole('region', { name: tab }); const size = within(panel).getByLabelText(/^每页数量/); expect(size.closest('.ui-pagination')).not.toBeNull(); expect(within(panel).getByRole('button', { name: '下一页' })).toBeDisabled(); fireEvent.click(size); fireEvent.click(screen.getByRole('option', { name: '50 条/页' })); fireEvent.click(screen.getByRole('tab', { name: '签名通道发送质量' })); expect( within(screen.getByRole('region', { name: '签名通道发送质量' })).getByLabelText(/^每页数量/), ).toHaveTextContent('25 条/页'); fireEvent.click(screen.getByRole('tab', { name: tab })); expect(size).toHaveTextContent('50 条/页'); expect(api.getSignatureRetirementHeatmap).toHaveBeenCalledTimes(1); }, ); });