feat: update session payment logic to ensure accurate payable amounts and reflect consumables in cost breakdown
- Adjusted the calculation of payable amounts in PaymentStep to align with server logic, ensuring overpayments are handled correctly. - Enhanced DetailsStep to include consumables in the itemized cost breakdown, ensuring consistency with patient share calculations. - Updated tests for SessionPaymentPage to validate new behavior regarding overpayments and consumable listings. - Modified PatientController to register SessionPayment correctly when settling sessions via wallet, preventing double charges. - Refactored WalletService to remove outdated methods and ensure wallet transactions reflect the correct amounts after discounts. - Improved accessibility in SearchableSelect component by adding aria labels and ensuring proper role attributes for screen readers. - Updated styles to ensure minimum touch targets meet WCAG guidelines for mobile usability.
This commit is contained in:
@@ -24,7 +24,8 @@ describe('PersianDateInput', () => {
|
||||
const onChange = vi.fn();
|
||||
const { container } = render(<PersianDateInput value="2024-03-25" onChange={onChange} />);
|
||||
fireEvent.click((container.firstChild as HTMLElement).firstChild as Element);
|
||||
const target = Array.from(container.querySelectorAll('button')).find(
|
||||
// تقویم با createPortal به document.body میرود، پس بیرون از container است
|
||||
const target = Array.from(document.body.querySelectorAll('button')).find(
|
||||
(b) => b.textContent?.trim() === '۱۵',
|
||||
);
|
||||
expect(target).toBeTruthy();
|
||||
|
||||
@@ -31,3 +31,25 @@ describe('SearchableSelect — تطبیق مقدار', () => {
|
||||
expect(screen.queryByText('تامین اجتماعی')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SearchableSelect — نام دسترسپذیر', () => {
|
||||
it('در نبود ariaLabel، از placeholder بهعنوان نام استفاده میکند', () => {
|
||||
render(<SearchableSelect options={insuranceOpts} onChange={() => {}} placeholder="نوع بیمه" />);
|
||||
expect(screen.getByRole('combobox', { name: 'نوع بیمه' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('ariaLabel بر placeholder اولویت دارد', () => {
|
||||
render(<SearchableSelect options={insuranceOpts} onChange={() => {}} placeholder="انتخاب کنید..." ariaLabel="بیمه پایه" />);
|
||||
expect(screen.getByRole('combobox', { name: 'بیمه پایه' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('ariaLabelledBy به label قابلمشاهده وصل میشود', () => {
|
||||
render(
|
||||
<>
|
||||
<span id="lbl-city">شهر</span>
|
||||
<SearchableSelect options={[{ value: 9, label: 'یزد' }]} onChange={() => {}} ariaLabelledBy="lbl-city" />
|
||||
</>,
|
||||
);
|
||||
expect(screen.getByRole('combobox', { name: 'شهر' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,14 @@ interface Props {
|
||||
noOptionsMessage?: string;
|
||||
inputId?: string;
|
||||
height?: number;
|
||||
/**
|
||||
* نام دسترسپذیر فیلد. react-select ورودی داخلی خودش را بدون label رندر میکند و
|
||||
* placeholder را هم بهصورت div میگذارد نه attribute، پس بدون این، فیلد برای
|
||||
* screen reader بینام میماند. پیشفرض روی placeholder میافتد.
|
||||
*/
|
||||
ariaLabel?: string;
|
||||
/** اگر label قابلمشاهدهای وجود دارد، id آن را بده (بر ariaLabel اولویت دارد). */
|
||||
ariaLabelledBy?: string;
|
||||
}
|
||||
|
||||
export default function SearchableSelect({
|
||||
@@ -31,6 +39,8 @@ export default function SearchableSelect({
|
||||
noOptionsMessage = 'موردی یافت نشد',
|
||||
inputId,
|
||||
height = 42,
|
||||
ariaLabel,
|
||||
ariaLabelledBy,
|
||||
}: Props) {
|
||||
const darkMode = useUiStore((s) => s.darkMode);
|
||||
|
||||
@@ -118,6 +128,8 @@ export default function SearchableSelect({
|
||||
noOptionsMessage={() => noOptionsMessage}
|
||||
loadingMessage={() => 'در حال بارگذاری...'}
|
||||
inputId={inputId}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
aria-label={ariaLabelledBy ? undefined : (ariaLabel ?? placeholder)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user