import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen, fireEvent } from '@testing-library/react'; import { renderWithProviders } from '../test/utils'; import { formatRial, tomanToRial } from '../lib/utils'; vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); vi.mock('../lib/api', () => ({ api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() }, ApiError: class extends Error {}, })); import { api } from '../lib/api'; import { useAuthStore } from '../stores/authStore'; import SmsWalletPage from './SmsWalletPage'; const get = api.get as ReturnType; /** * مبلغِ واردشده اعتبارِ کیف پول است — خالص. مالیات رویش اضافه می‌شود، پس عددِ * دکمهٔ پرداخت باید بزرگ‌تر از مبلغ واردشده باشد وگرنه کاربر سرِ درگاه غافلگیر * می‌شود. */ function mockApi(taxPercent: number) { get.mockImplementation((url: string) => { if (url.includes('/sms/wallet/balance')) { return Promise.resolve({ success: true, data: { balance_rials: 0, sms_price_rials: 5000 } }); } if (url.includes('/sms/wallet/logs')) { return Promise.resolve({ success: true, data: [], meta: { totalRecords: 0, totalPages: 0, currentPage: 1 } }); } if (url.includes('/sms/settings')) return Promise.resolve({ success: true, data: null }); // FeatureGate صفحه را پشت قابلیت پنل پیامک نگه می‌دارد. if (url.includes('/subscription/my')) { return Promise.resolve({ success: true, data: { subscription: null, used_trial: false, effective_plan: { features: { sms_panel: true } } }, }); } if (url.includes('/payment/config')) { return Promise.resolve({ success: true, data: { test_mode: false, appointment_fee_rials: 0, tax_percent: taxPercent, gateways: [{ name: 'mellat', label: 'بانک ملت' }] }, }); } return Promise.resolve({ success: true, data: null }); }); } async function openChargeModalWith(amountToman: string) { renderWithProviders(, { route: '/admin/sms-wallet' }); fireEvent.click(await screen.findByText('شارژ کیف پول')); const input = await screen.findByLabelText('مبلغ شارژ (تومان)'); fireEvent.change(input, { target: { value: amountToman } }); } describe('SmsWalletPage — tax', () => { beforeEach(() => { get.mockReset(); // useSubscription فقط برای این نقش‌ها کوئری می‌زند؛ بدونش FeatureGate صفحه را می‌بندد. useAuthStore.setState({ primaryRole: 'doctor', context: null } as any); }); it('adds tax on top of the credit and shows the split', async () => { mockApi(10); await openChargeModalWith('100000'); const net = tomanToRial(100000); expect(await screen.findByText('اعتباری که به کیف پول اضافه می‌شود')).toBeInTheDocument(); expect(screen.getByText('مالیات بر ارزش افزوده ۱۰٪')).toBeInTheDocument(); expect(screen.getByText(formatRial(net * 0.1))).toBeInTheDocument(); expect(screen.getAllByText(formatRial(net * 1.1)).length).toBeGreaterThan(0); }); it('shows no tax row and charges the plain amount when tax is off', async () => { mockApi(0); await openChargeModalWith('100000'); expect(await screen.findByText(/پرداخت .* از طریق بانک ملت/)).toBeInTheDocument(); expect(screen.queryByText('اعتباری که به کیف پول اضافه می‌شود')).not.toBeInTheDocument(); expect(screen.getByText(new RegExp(formatRial(tomanToRial(100000))))).toBeInTheDocument(); }); });