import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mount, flushPromises } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import TransactionsTable from '../../resources/js/components/billing/TransactionsTable.vue'; import * as billingApi from '../../resources/js/api/billing'; import type { BillingTransaction, TransactionsPage } from '../../resources/js/api/billing'; vi.mock('../../resources/js/api/billing'); const vuetify = createVuetify(); function txn(over: Partial = {}): BillingTransaction { return { id: 1, code: 'TX-1', type: 'topup', description: 'Пополнение баланса', amount_rub: '5000.00', amount_leads: 0, balance_rub_after: '5000.00', display_amount_rub: '5000.00', created_at: '2026-05-10T14:21:00Z', ...over, }; } function makePage(txns: BillingTransaction[]): TransactionsPage { return { data: txns, meta: { current_page: 1, last_page: 1, total: txns.length, per_page: 20 } }; } describe('TransactionsTable.vue', () => { beforeEach(() => { vi.mocked(billingApi.getTransactions).mockResolvedValue(makePage([txn()])); }); it('загружает транзакции при монтировании', async () => { const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(billingApi.getTransactions).toHaveBeenCalled(); expect((wrapper.vm as unknown as { total: number }).total).toBe(1); }); it('смена таба «Пополнения» шлёт type=topup', async () => { const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); await (wrapper.vm as unknown as { changeTab: (id: string) => Promise }).changeTab('topup'); expect(billingApi.getTransactions).toHaveBeenLastCalledWith( expect.objectContaining({ type: 'topup' }), ); }); it('таб «Все» не шлёт type', async () => { const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); await (wrapper.vm as unknown as { changeTab: (id: string) => Promise }).changeTab('all'); const lastCall = vi.mocked(billingApi.getTransactions).mock.calls.at(-1)?.[0]; expect(lastCall).not.toHaveProperty('type'); }); it('показывает error-alert при сбое', async () => { vi.mocked(billingApi.getTransactions).mockRejectedValue(new Error('fail')); const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(wrapper.text()).toContain('Не удалось загрузить транзакции'); }); it('does NOT render «Возвраты» tab', async () => { const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); expect(wrapper.text()).not.toContain('Возвраты'); }); it('uses display_amount_rub for amount column on historic prepaid rows', async () => { // historic prepaid row: amount_rub='0.00', amount_leads=null, display_amount_rub='0.00' vi.mocked(billingApi.getTransactions).mockResolvedValue( makePage([ txn({ type: 'historical_import', amount_rub: '0.00', amount_leads: null, display_amount_rub: '0.00', }), ]), ); const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); // Old code would have shown '− 1 лид.' for amount_leads=-1; new code uses display_amount_rub // With display_amount_rub='0.00' we expect '0' somewhere in the amount cell (formatted as 0 ₽) expect(wrapper.text()).not.toContain('лид.'); }); it('formats date with 2-digit year', async () => { const wrapper = mount(TransactionsTable, { global: { plugins: [vuetify] } }); await flushPromises(); // Date '2026-05-10T14:21:00Z' formatted as ru-RU with year:'2-digit' → includes '26' // Pattern: DD.MM.YY, HH:MM (ru-RU locale with 2-digit year) const text = wrapper.text(); expect(text).toMatch(/\d{2}\.\d{2}\.\d{2}/); }); });