Files
clinicpro/assets/admin/components/WalletTransactionModal.test.tsx
T
hamedandClaude Opus 4.8 f028d6841a feat: port wallet charge/withdraw modal from tauri to patient admin page
Add manual wallet withdrawal (debit) endpoint mirroring the offline app's
balance guard, and rebuild the patient کیف پول tab around a single
charge/withdraw toggle modal (quick amounts, تومان→ریال conversion,
transaction filters).

Backend:
- POST /api/v1/patient/{uuid}/wallet/withdraw — creates a debit
  WalletTransaction; 422 ERR_WALLET_INSUFFICIENT when amount exceeds balance.
- ErrorCodes: ERR_WALLET_INSUFFICIENT ('موجودی کیف پول کافی نیست').
- docs/api/patient.md updated.

Frontend:
- usePatientWallet hook (balance + charge/withdraw mutations).
- WalletTransactionModal (toggle, quick amounts, UI-only payment fields).
- WalletTab: charge button, همه/واریزی/برداشت filters.

Tests: backend withdraw success/insufficient/non-positive/ownership;
frontend modal + wallet tab interactions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:56:39 +03:30

57 lines
2.9 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../test/utils';
import WalletTransactionModal from './WalletTransactionModal';
// موجودی نمونه: ۳۰۰٬۰۰۰ ریال = ۳۰٬۰۰۰ تومان
const BALANCE_RIALS = 300_000;
function open(onSubmit = vi.fn()) {
renderWithProviders(
<WalletTransactionModal open balanceRials={BALANCE_RIALS} onClose={vi.fn()} onSubmit={onSubmit} />,
);
return onSubmit;
}
describe('WalletTransactionModal (شارژ/برداشت کیف پول)', () => {
it('renders both mode tabs, the balance banner and the quick amounts', () => {
open();
expect(screen.getByRole('button', { name: 'شارژ کیف پول' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'برداشت از کیف پول' })).toBeInTheDocument();
expect(screen.getByText('موجودی کیف پول')).toBeInTheDocument();
// چهار چیپ مبلغ سریع، هر کدام «... تومان»
expect(screen.getAllByRole('button', { name: /تومان/ }).length).toBe(4);
});
it('submits a charge with the amount converted from toman to rials', () => {
const onSubmit = open();
fireEvent.change(screen.getByPlaceholderText('مبلغ دلخواه (تومان)'), { target: { value: '100000' } });
fireEvent.click(screen.getByRole('button', { name: 'ثبت تراکنش' }));
expect(onSubmit).toHaveBeenCalledWith({ mode: 'charge', amount_rials: 1_000_000 });
});
it('submits a withdraw (debit) when the برداشت tab is active and amount is within balance', () => {
const onSubmit = open();
fireEvent.click(screen.getByRole('button', { name: 'برداشت از کیف پول' }));
fireEvent.change(screen.getByPlaceholderText('مبلغ دلخواه (تومان)'), { target: { value: '20000' } });
fireEvent.click(screen.getByRole('button', { name: 'ثبت تراکنش' }));
expect(onSubmit).toHaveBeenCalledWith({ mode: 'withdraw', amount_rials: 200_000 });
});
it('blocks a withdraw above the balance and shows the insufficient-funds hint', () => {
const onSubmit = open();
fireEvent.click(screen.getByRole('button', { name: 'برداشت از کیف پول' }));
// ۴۰٬۰۰۰ تومان = ۴۰۰٬۰۰۰ ریال > موجودی ۳۰۰٬۰۰۰ ریال
fireEvent.change(screen.getByPlaceholderText('مبلغ دلخواه (تومان)'), { target: { value: '40000' } });
expect(screen.getByText('موجودی کیف پول کافی نیست')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'ثبت تراکنش' })).toBeDisabled();
fireEvent.click(screen.getByRole('button', { name: 'ثبت تراکنش' }));
expect(onSubmit).not.toHaveBeenCalled();
});
it('keeps submit disabled when no amount is entered', () => {
open();
expect(screen.getByRole('button', { name: 'ثبت تراکنش' })).toBeDisabled();
});
});