import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { createVuetify } from 'vuetify'; import axios from 'axios'; import ChargesTab from '../../resources/js/views/billing/ChargesTab.vue'; vi.mock('axios'); // vite-plugin-vuetify auto-import регистрируется через vitest.config.ts, // поэтому createVuetify() без явного передачи components работает. const vuetify = createVuetify(); const mockData = { data: [ { id: 1, charged_at: '2026-05-01T10:00:00Z', deal_id: 42, tier_no: 1, charge_source: 'rub', price_per_lead_kopecks: 50000, }, { id: 2, charged_at: '2026-05-01T11:00:00Z', deal_id: 43, tier_no: 1, charge_source: 'prepaid', price_per_lead_kopecks: 0, }, ], meta: { current_page: 1, last_page: 1, total: 2, per_page: 20 }, }; describe('ChargesTab', () => { beforeEach(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (axios.get as any).mockResolvedValue({ data: mockData }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (axios.post as any).mockResolvedValue({ data: new Blob() }); globalThis.URL.createObjectURL = vi.fn(() => 'blob:url'); globalThis.URL.revokeObjectURL = vi.fn(); }); it('renders 2 charge rows from API', async () => { const wrapper = mount(ChargesTab, { global: { plugins: [vuetify], stubs: { RouterLink: true } }, }); await new Promise((r) => setTimeout(r, 50)); // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((wrapper.vm as any).total).toBe(2); }); it('period change triggers refetch', async () => { const wrapper = mount(ChargesTab, { global: { plugins: [vuetify], stubs: { RouterLink: true } }, }); await new Promise((r) => setTimeout(r, 50)); // eslint-disable-next-line @typescript-eslint/no-explicit-any (wrapper.vm as any).period = 'last_month'; // eslint-disable-next-line @typescript-eslint/no-explicit-any await (wrapper.vm as any).refresh(); expect(axios.get).toHaveBeenLastCalledWith('/api/billing/charges', { params: expect.objectContaining({ period: 'last_month' }), }); }); it('does NOT render «Источник» filter dropdown', () => { const wrapper = mount(ChargesTab, { global: { plugins: [vuetify], stubs: { RouterLink: true } }, }); expect(wrapper.text()).not.toContain('Источник'); }); it('does NOT include charge_source in table headers', async () => { const wrapper = mount(ChargesTab, { global: { plugins: [vuetify], stubs: { RouterLink: true } }, }); await new Promise((r) => setTimeout(r, 50)); const headers = wrapper.findAll('th').map((th) => th.text()); expect(headers.some((h) => /Источник/.test(h))).toBe(false); }); it('shows «из бесплатного» caption for historic prepaid rows (price_per_lead_kopecks=0)', async () => { const wrapper = mount(ChargesTab, { global: { plugins: [vuetify], stubs: { RouterLink: true } }, }); await new Promise((r) => setTimeout(r, 50)); expect(wrapper.text()).toContain('из бесплатного'); }); it('exportCsv fires POST /export with blob response without charge_source param', async () => { const wrapper = mount(ChargesTab, { global: { plugins: [vuetify], stubs: { RouterLink: true } }, }); await new Promise((r) => setTimeout(r, 50)); // eslint-disable-next-line @typescript-eslint/no-explicit-any await (wrapper.vm as any).exportCsv(); expect(axios.post).toHaveBeenCalledWith( '/api/billing/charges/export', expect.objectContaining({ period: 'current_month' }), expect.objectContaining({ responseType: 'blob' }), ); // charge_source must NOT be in params const callArgs = (axios.post as ReturnType).mock.calls[0][1] as Record; expect(callArgs).not.toHaveProperty('charge_source'); }); });