import ElementPlus from 'element-plus' import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { getCustomerVisit, listCustomerVisits, saveCustomerVisit, uploadCustomerVisitAttachment } from '../api/customerVisits' import CustomerVisitsView from './CustomerVisitsView.vue' import { commonPhase5Stubs } from './phase5TestStubs' const canPermission = vi.hoisted(() => vi.fn((_code: string) => false)) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) })) vi.mock('../api/customerVisits', () => ({ listCustomerVisits: vi.fn(), getCustomerVisit: vi.fn(), saveCustomerVisit: vi.fn(), uploadCustomerVisitAttachment: vi.fn() })) const visit = { id: '9', customerId: '100', deliveryNo: 'DEL-1', deliveryCompletedAt: '2026-09-01T10:00:00', status: 'PENDING' as const, visitCount: 0, attachments: [], } function mountView() { return mount(CustomerVisitsView, { global: { plugins: [ElementPlus], stubs: commonPhase5Stubs } }) } describe('CustomerVisitsView permissions and visit material', () => { beforeEach(() => { canPermission.mockReset().mockReturnValue(false) vi.mocked(listCustomerVisits).mockReset().mockResolvedValue({ records: [visit], current: 1, size: 10, total: 1, pages: 1, visitable: 1, pending: 1, completed: 0 }) vi.mocked(getCustomerVisit).mockReset().mockResolvedValue({ visit, inboundOrders: [{ inboundOrderId: '7', inboundCode: 'IN-1', lastMileOrderId: '6', lastMileOrderNo: 'LM-1', packageCount: 2 }], customerFeedback: '', remark: '', }) vi.mocked(saveCustomerVisit).mockReset().mockResolvedValue({ visit, inboundOrders: [] }) vi.mocked(uploadCustomerVisitAttachment).mockReset().mockResolvedValue({ fileId: '8', fileName: 'visit.pdf', sizeBytes: 4 }) }) it('hides the visit action without edit permission', async () => { const wrapper = mountView() await flushPromises() expect(wrapper.find('[data-testid="data-table"]').text()).not.toContain('回访') wrapper.unmount() }) it('validates and submits a customer-scoped visit', async () => { canPermission.mockImplementation(code => code.endsWith(':edit')) const wrapper = mountView() await flushPromises() await wrapper.findAll('button').find(button => button.text() === '回访')!.trigger('click') await flushPromises() expect(getCustomerVisit).toHaveBeenCalledWith('9', '100') const dialog = wrapper.find('[data-dialog="客户回访"]') await dialog.find('.dialog-confirm').trigger('click') expect(saveCustomerVisit).not.toHaveBeenCalled() await dialog.find('select').setValue('NORMAL') const textareas = dialog.findAll('textarea') await textareas[0].setValue(' 服务正常 ') await textareas[1].setValue(' 后续关注 ') await dialog.find('.dialog-confirm').trigger('click') await flushPromises() expect(saveCustomerVisit).toHaveBeenCalledWith('9', '100', { selectedInboundOrderId: '7', visitResult: 'NORMAL', customerFeedback: '服务正常', remark: '后续关注', attachments: [], }) wrapper.unmount() }) it('uploads the selected attachment in the visit customer scope', async () => { canPermission.mockImplementation(code => code.endsWith(':edit')) const wrapper = mountView() await flushPromises() await wrapper.findAll('button').find(button => button.text() === '回访')!.trigger('click') await flushPromises() const input = wrapper.find('[data-dialog="客户回访"] input[type="file"]') const inputClick = vi.spyOn(input.element as HTMLInputElement, 'click').mockImplementation(() => {}) await wrapper.findAll('button').find(button => button.text() === '选择文件')!.trigger('click') expect(inputClick).toHaveBeenCalledOnce() inputClick.mockRestore() const file = new File(['data'], 'visit.pdf', { type: 'application/pdf' }) Object.defineProperty(input.element, 'files', { configurable: true, value: [file] }) await input.trigger('change') await flushPromises() expect(uploadCustomerVisitAttachment).toHaveBeenCalledWith('9', '100', file) expect(wrapper.text()).toContain('visit.pdf') wrapper.unmount() }) })