import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import ElementPlus from 'element-plus' import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' import { createMemoryHistory, createRouter } from 'vue-router' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { listPlatformTenants } from '@/modules/iam' import { getReceivableStatistics } from '../api/finance' import type { ReceivableStatisticsResult } from '../types' import FinanceReceivableStatisticsView from './FinanceReceivableStatisticsView.vue' const can = vi.hoisted(() => vi.fn((_permission: string) => true)) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) })) vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn() })) vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() })) vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() })) vi.mock('../api/finance', () => ({ getReceivableStatistics: vi.fn() })) const page = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceReceivableStatisticsView.vue'), 'utf8') const statistics: ReceivableStatisticsResult = { statisticsMonth: '2026-10', currency: 'CNY', totalReceivable: '2600', totalReceived: '2550', totalOutstanding: '50', totalCustomers: 2, page: 1, pageSize: 20, records: [ { customerId: '10001', customerName: '客户A', currency: 'CNY', totalReceivable: '2400', totalReceived: '2400', totalOutstanding: '0' }, { customerId: '10002', customerName: '客户 10002', currency: 'CNY', totalReceivable: '200', totalReceived: '150', totalOutstanding: '50' }, ], } async function mountPage() { const router = createRouter({ history: createMemoryHistory(), routes: [ { path: '/finance/receivable-statistics', name: 'finance-receivable-statistics', component: FinanceReceivableStatisticsView }, { path: '/other', name: 'other-page', component: { template: '
其他页面
' } }, ] }) await router.push('/finance/receivable-statistics') await router.isReady() const wrapper = mount(FinanceReceivableStatisticsView, { attachTo: document.body, global: { plugins: [ElementPlus, router] } }) await flushPromises() return { wrapper, router } } enableAutoUnmount(afterEach) beforeEach(() => { can.mockReturnValue(true) vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} }) vi.mocked(listPlatformTenants).mockResolvedValue({ records: [{ id: '10002', tenantName: '客户B', status: 'ACTIVE' } as never], page: 1, size: 200, total: 1, pages: 1 }) vi.mocked(getReceivableStatistics).mockResolvedValue(statistics) }) afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); document.body.innerHTML = '' }) describe('receivable statistics page', () => { it('renders only the approved CNY derived metrics and current-filter actions', () => { for (const label of ['统计月份', '应收单位', '应收总额', '已收合计', '余额未收', '打印', '导出Excel']) expect(page).toContain(label) expect(page).toContain("exportType: 'FINANCE_RECEIVABLE_STATISTICS'") expect(page).toContain('window.print()') expect(page).toContain('aria-label="统计月份"') expect(page).toContain('aria-label="应收单位"') for (const removed of ['统计月份', '应收单位', '#before-results', '应收款明细', '仅统计CNY正式应收结算单']) expect(page).not.toContain(removed) expect(page).toContain('displayCustomerName(row)') expect(page).toContain('/^客户\\s*\\d+$/.test(snapshot)') expect(page).toContain("return masterName || '—'") for (const excluded of ['FBA直发', 'FBA专线', '零头减免', 'USD', '>编辑<']) expect(page).not.toContain(excluded) }) it('keeps query reset, loading, empty, error and permission states explicit', () => { expect(page).toContain('function reset()') expect(page).toContain('无权访问应收款统计表') expect(page).toContain(' { const print = vi.spyOn(window, 'print').mockImplementation(() => {}) const { wrapper } = await mountPage() const report = document.body.querySelector(':scope > .receivable-statistics-print')! expect(report).not.toBeNull() expect(report.querySelector('h1')?.textContent).toBe('应收款统计表') expect(report.textContent).toContain('统计月份:2026-10') expect([...report.querySelectorAll('thead th')].map(cell => cell.textContent)).toEqual(['应收单位', '币种', '应收总额(CNY)', '已收合计(CNY)', '余额未收(CNY)']) expect([...report.querySelectorAll('tbody tr')].map(row => [...row.querySelectorAll('td')].map(cell => cell.textContent))).toEqual([ ['客户A', 'CNY', '2,400.00', '2,400.00', '0.00'], ['客户B', 'CNY', '200.00', '150.00', '50.00'], ]) expect([...report.querySelectorAll('tfoot td')].map(cell => cell.textContent)).toEqual(['2,600.00', '2,550.00', '50.00']) expect(report.querySelector('button, input, .finance-metric-grid, .data-table__pagination')).toBeNull() expect(page).toContain(':global(body:has(> .receivable-statistics-print) > :not(.receivable-statistics-print)) { display: none !important; }') expect(page).toContain('width: 100%; border-collapse: collapse; table-layout: fixed;') await wrapper.findAll('button').find(button => button.text() === '打印')!.trigger('click') expect(print).toHaveBeenCalledOnce() }) it('removes the print report when a cached statistics page is no longer the active route', async () => { const { router } = await mountPage() expect(document.querySelector('.receivable-statistics-print')).not.toBeNull() await router.push('/other') await flushPromises() expect(document.querySelector('.receivable-statistics-print')).toBeNull() await router.push('/finance/receivable-statistics') await flushPromises() expect(document.querySelector('.receivable-statistics-print')).not.toBeNull() }) it.each(['loading', 'error', 'no-permission'])('does not print unavailable statistics: %s', async state => { if (state === 'loading') vi.mocked(getReceivableStatistics).mockReturnValueOnce(new Promise(() => {})) if (state === 'error') vi.mocked(getReceivableStatistics).mockRejectedValueOnce(new Error('不可用')) if (state === 'no-permission') can.mockReturnValue(false) const print = vi.spyOn(window, 'print').mockImplementation(() => {}) const { wrapper } = await mountPage() const vm = wrapper.vm as unknown as { printCurrent(): void } expect(document.querySelector('.receivable-statistics-print')).toBeNull() vm.printCurrent() expect(print).not.toHaveBeenCalled() }) })