Add tests and implementation for ServiceDetailPage and PriceInput components

- Implement PriceInput component tests to validate Persian and Arabic numeral handling, input formatting, and controlled behavior.
- Create ServiceDetailPage component with detailed service information, including pricing, insurance coverage, and editing capabilities.
- Add API tests for service item detail retrieval and coverage synchronization with insurance contracts.
- Ensure proper error handling and user feedback for service item retrieval and coverage management.
This commit is contained in:
hamed
2026-07-18 12:10:49 +03:30
parent f1d58e1604
commit 42d9ad26c5
27 changed files with 1758 additions and 284 deletions
@@ -0,0 +1,118 @@
import { describe, it, expect, vi } from 'vitest';
import { useState } from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import PriceInput from './PriceInput';
/** پوشش حالت واقعی: مقدار توسط والد نگه داشته می‌شود (controlled). */
function Controlled({ initial = 0, ...rest }: { initial?: number } & Record<string, unknown>) {
const [value, setValue] = useState(initial);
return (
<>
<PriceInput value={value} onChange={setValue} {...rest} />
<span data-testid="value">{value}</span>
</>
);
}
const input = () => screen.getByRole('textbox') as HTMLInputElement;
const emitted = () => screen.getByTestId('value').textContent;
describe('PriceInput', () => {
it('رقم فارسی را می‌پذیرد و عدد لاتین می‌دهد', () => {
render(<Controlled />);
fireEvent.change(input(), { target: { value: '۸۵۰۰۰' } });
expect(emitted()).toBe('85000');
expect(input().value).toBe('۸۵٬۰۰۰');
});
it('رقم عربی را می‌پذیرد', () => {
render(<Controlled />);
fireEvent.change(input(), { target: { value: '٢٥٠' } });
expect(emitted()).toBe('250');
});
it('پیست با واحد و جداکننده پاک‌سازی می‌شود', () => {
render(<Controlled />);
fireEvent.change(input(), { target: { value: '۸۵,۰۰۰ تومان' } });
expect(emitted()).toBe('85000');
});
it('صفرِ تایپ‌شده حفظ می‌شود و به خالی تبدیل نمی‌شود', () => {
render(<Controlled />);
fireEvent.change(input(), { target: { value: '۰' } });
expect(input().value).toBe('۰');
expect(emitted()).toBe('0');
});
it('پاک‌کردن فیلد ⇒ خالی و مقدار صفر', () => {
render(<Controlled initial={5000} />);
fireEvent.change(input(), { target: { value: '' } });
expect(input().value).toBe('');
expect(emitted()).toBe('0');
});
it('مقدار اولیه صفر ⇒ فیلد خالی (نه ۰)', () => {
render(<Controlled initial={0} />);
expect(input().value).toBe('');
});
it('min حین تایپ اعمال نمی‌شود ولی روی blur اعمال می‌شود', () => {
render(<Controlled min={100} />);
fireEvent.change(input(), { target: { value: '۵' } });
expect(emitted()).toBe('5');
fireEvent.blur(input());
expect(emitted()).toBe('100');
expect(input().value).toBe('۱۰۰');
});
it('max روی blur اعمال می‌شود', () => {
render(<Controlled max={1000} />);
fireEvent.change(input(), { target: { value: '۵۰۰۰' } });
fireEvent.blur(input());
expect(emitted()).toBe('1000');
});
it('فیلد خالی روی blur به min پرت نمی‌شود', () => {
render(<Controlled min={100} />);
fireEvent.blur(input());
expect(input().value).toBe('');
expect(emitted()).toBe('0');
});
it('حروف نامعتبر حذف می‌شوند و هرگز NaN نمی‌دهد', () => {
render(<Controlled />);
fireEvent.change(input(), { target: { value: 'ابج' } });
expect(input().value).toBe('');
expect(emitted()).toBe('0');
expect(emitted()).not.toContain('NaN');
});
it('latin ارقام لاتین با کاما نمایش می‌دهد', () => {
render(<Controlled latin />);
fireEvent.change(input(), { target: { value: '۱۲۰۰' } });
expect(input().value).toBe('1,200');
});
it('suffix واحد را کنار فیلد نشان می‌دهد', () => {
render(<Controlled suffix="تومان" />);
expect(screen.getByText('تومان')).toBeTruthy();
});
it('تغییر مقدار از بیرون متن فیلد را همگام می‌کند', () => {
const { rerender } = render(<PriceInput value={1000} onChange={vi.fn()} />);
expect(input().value).toBe('۱٬۰۰۰');
rerender(<PriceInput value={0} onChange={vi.fn()} />);
expect(input().value).toBe('');
});
});