import { describe, it, expect, vi } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import InvoicesTable from '../../resources/js/components/billing/InvoicesTable.vue'; import * as billingApi from '../../resources/js/api/billing'; import type { BillingInvoice } from '../../resources/js/api/billing'; vi.mock('../../resources/js/api/billing'); const vuetify = createVuetify(); function inv(over: Partial = {}): BillingInvoice { return { id: 1, invoice_number: 'СЧ-2026-00001', amount_total: '990.00', status: 'issued', issued_at: '2026-05-07T00:00:00Z', expires_at: '2026-05-14T00:00:00Z', has_pdf: true, has_act: false, pdf_url: '/api/billing/invoices/1/pdf', act_url: null, ...over, }; } describe('InvoicesTable.vue', () => { it('показывает empty-state без счетов', async () => { vi.mocked(billingApi.getInvoices).mockResolvedValue({ data: [] }); const wrapper = mount(InvoicesTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(wrapper.text()).toContain('появятся'); }); it('рендерит строки счетов из API', async () => { vi.mocked(billingApi.getInvoices).mockResolvedValue({ data: [inv()] }); const wrapper = mount(InvoicesTable, { global: { plugins: [vuetify] } }); await flushPromises(); const text = wrapper.text(); expect(text).toContain('СЧ-2026-00001'); expect(text).toContain('Выставлен'); }); it('кнопка «Счёт» disabled при has_pdf=false и активна при has_pdf=true', async () => { const invs: BillingInvoice[] = [ inv({ id: 1, invoice_number: 'СЧ-2026-00010', has_pdf: false, pdf_url: null }), inv({ id: 2, invoice_number: 'СЧ-2026-00011', status: 'paid', has_pdf: true }), ]; vi.mocked(billingApi.getInvoices).mockResolvedValue({ data: invs }); const wrapper = mount(InvoicesTable, { global: { plugins: [vuetify] } }); await flushPromises(); const pdfButtons = wrapper.findAll('button, a').filter((b) => b.text().includes('Счёт')); expect(pdfButtons).toHaveLength(2); // Строка 1 (has_pdf=false) → disabled; строка 2 (has_pdf=true) → активна. expect(pdfButtons[0].attributes('disabled')).toBeDefined(); expect(pdfButtons[1].attributes('disabled')).toBeUndefined(); }); it('показывает кнопку «Акт» только при has_act=true', async () => { vi.mocked(billingApi.getInvoices).mockResolvedValue({ data: [inv({ id: 7, status: 'paid', has_act: true, act_url: '/api/billing/invoices/7/act' })], }); const wrapper = mount(InvoicesTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(wrapper.find('[data-testid="inv-act-7"]').exists()).toBe(true); }); it('показывает error-alert при сбое', async () => { vi.mocked(billingApi.getInvoices).mockRejectedValue(new Error('fail')); const wrapper = mount(InvoicesTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(wrapper.text()).toContain('Не удалось загрузить счета'); }); it('renders amount_total with ₽ suffix', async () => { vi.mocked(billingApi.getInvoices).mockResolvedValue({ data: [inv({ invoice_number: 'INV-1', amount_total: '1234.00', status: 'paid' })], }); const wrapper = mount(InvoicesTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(wrapper.text()).toMatch(/1\s?234\s?₽/); }); });