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>
57 lines
2.9 KiB
TypeScript
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();
|
|
});
|
|
});
|