- Replaced raw input fields for pricing with PriceInput component across various forms and modals to ensure consistent formatting and accessibility. - Updated tests to reflect changes in pricing input handling, ensuring values are displayed in toman with proper formatting. - Enhanced accessibility by adding aria-labels and aria-invalid attributes to PriceInput components. - Adjusted UI elements to improve layout and user experience, particularly in forms related to service items and scheduling. - Changed labels from "نمایش در نوبتدهی" to "نمایش در نوبتدهی آنلاین" for clarity.
121 lines
5.1 KiB
TypeScript
121 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { renderWithProviders } from '../../test/utils';
|
|
|
|
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
|
|
|
|
import ResourceServicesPanel from './ResourceServicesPanel';
|
|
|
|
const resource = { uuid: 'r-1', name: 'دستگاه شمارهٔ ۲' } as never;
|
|
|
|
/** PriceInput مبلغ را با جداکنندهٔ fa-IR نشان میدهد، نه رقمِ خام. */
|
|
const fa = (n: number) => new Intl.NumberFormat('fa-IR').format(n);
|
|
|
|
const services = [
|
|
{ uuid: 's-1', name: 'لیزر پا', duration_minutes: 30, price_rials: 8_000_000 },
|
|
{ uuid: 's-2', name: 'لیزر دست', duration_minutes: 20, price_rials: 5_000_000 },
|
|
];
|
|
|
|
/** ردیفی که مدتش را خودِ منبع گفته ولی قیمتش را از سرویس ارث برده. */
|
|
const offerings = [
|
|
{
|
|
service_uuid: 's-1',
|
|
service_name: 'لیزر پا',
|
|
duration_minutes: 15,
|
|
price_rials: null,
|
|
active: true,
|
|
effective_duration_minutes: 15,
|
|
effective_price_rials: 8_000_000,
|
|
duration_source: 'resource_option',
|
|
price_source: 'service_default',
|
|
},
|
|
];
|
|
|
|
function props(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
resource,
|
|
offerings: offerings as never,
|
|
services: services as never,
|
|
saving: false,
|
|
onCancel: vi.fn(),
|
|
onSave: vi.fn(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* پنل مستقیماً تست میشود، نه از راه مودال: مودالِ فهرست منابع حذف شد چون همان کار
|
|
* را تبِ «سرویسها»ی خودِ منبع میکند. منطقِ ارثبری/override همان است و باید بماند.
|
|
*/
|
|
describe('ResourceServicesPanel', () => {
|
|
it('نشان میدهد مقدار ارثبریشده از کجا آمده', () => {
|
|
renderWithProviders(<ResourceServicesPanel {...props()} />);
|
|
|
|
// مدت را خودِ منبع گفته، پس در خانه است.
|
|
expect(screen.getByDisplayValue('15')).toBeInTheDocument();
|
|
|
|
// قیمت تنظیم نشده: خانه خالی است و مقدار مؤثر با برچسبِ منبعش در placeholder.
|
|
const price = screen.getByPlaceholderText(/پیشفرض سرویس/);
|
|
expect(price).toHaveValue('');
|
|
});
|
|
|
|
it('قیمت به تومان گرفته میشود و به ریال ذخیره میشود', async () => {
|
|
const onSave = vi.fn();
|
|
renderWithProviders(<ResourceServicesPanel {...props({ onSave })} />);
|
|
|
|
// برچسب و placeholder هر دو توماناند؛ پیش از این برچسب «ریال» بود و ورودی
|
|
// کاربر ده برابر کمتر از چیزی میشد که فکر میکرد وارد کرده.
|
|
expect(screen.getByText('قیمت (تومان)')).toBeInTheDocument();
|
|
expect(screen.queryByText('قیمت (ریال)')).not.toBeInTheDocument();
|
|
|
|
const price = screen.getByPlaceholderText(/پیشفرض سرویس/);
|
|
await userEvent.type(price, '950000');
|
|
await userEvent.click(screen.getByRole('button', { name: 'ذخیره' }));
|
|
|
|
expect(onSave).toHaveBeenCalledWith([
|
|
{ service_uuid: 's-1', duration_minutes: '15', price_rials: '9500000', active: true },
|
|
]);
|
|
});
|
|
|
|
it('قیمتِ override شده به تومان در فیلد مینشیند', () => {
|
|
renderWithProviders(<ResourceServicesPanel {...props({
|
|
offerings: [{ ...offerings[0], price_rials: 9_500_000, price_source: 'resource_option' }] as never,
|
|
})} />);
|
|
|
|
expect(screen.getByDisplayValue(fa(950_000))).toBeInTheDocument();
|
|
});
|
|
|
|
it('سرویس تازه با مدت و قیمت پیشفرض خودش پر میشود', async () => {
|
|
const onSave = vi.fn();
|
|
renderWithProviders(<ResourceServicesPanel {...props({ onSave })} />);
|
|
|
|
await userEvent.click(screen.getByText('یک سرویس انتخاب کنید'));
|
|
await userEvent.click(await screen.findByText('لیزر دست'));
|
|
|
|
// ۵٬۰۰۰٬۰۰۰ ریال = ۵۰۰٬۰۰۰ تومان
|
|
expect(screen.getByDisplayValue(fa(500_000))).toBeInTheDocument();
|
|
expect(screen.getByDisplayValue('20')).toBeInTheDocument();
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'ذخیره' }));
|
|
|
|
expect(onSave).toHaveBeenCalledWith([
|
|
{ service_uuid: 's-1', duration_minutes: '15', price_rials: '', active: true },
|
|
{ service_uuid: 's-2', duration_minutes: '20', price_rials: '5000000', active: true },
|
|
]);
|
|
});
|
|
|
|
it('پاککردن مقدار یعنی بازگشت به ارث، نه صفر', async () => {
|
|
const onSave = vi.fn();
|
|
renderWithProviders(<ResourceServicesPanel {...props({ onSave })} />);
|
|
|
|
await userEvent.clear(screen.getByDisplayValue('15'));
|
|
await userEvent.click(screen.getByRole('button', { name: 'ذخیره' }));
|
|
|
|
// رشتهٔ خالی به سرور میرود و سرور آن را `null` میفهمد — نه `0`.
|
|
expect(onSave).toHaveBeenCalledWith([
|
|
{ service_uuid: 's-1', duration_minutes: '', price_rials: '', active: true },
|
|
]);
|
|
});
|
|
});
|