- 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.
287 lines
10 KiB
TypeScript
287 lines
10 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
||
import { z } from 'zod';
|
||
import {
|
||
digitsOnly,
|
||
formatYear,
|
||
parseUserNumber,
|
||
parseUserNumberClamped,
|
||
persianSafeNumber,
|
||
iranNationalCodeSchema,
|
||
iranNationalCodeOptionalSchema,
|
||
formatRial,
|
||
rialToToman,
|
||
tomanToRial,
|
||
formatNumber,
|
||
toDate,
|
||
formatDate,
|
||
formatDateTime,
|
||
toGregorianDate,
|
||
maskMobile,
|
||
cn,
|
||
toEnglishDigits,
|
||
sanitizeMobileInput,
|
||
isValidIranMobile,
|
||
IRAN_MOBILE_RE,
|
||
iranMobileSchema,
|
||
iranMobileOptionalSchema,
|
||
} from '@/lib/utils';
|
||
|
||
const PERSIAN_DIGITS = /[۰-۹]/;
|
||
|
||
describe('formatRial', () => {
|
||
it('مقدار ریال را ÷۱۰ به تومان تبدیل و با پسوند تومان برمیگرداند', () => {
|
||
const out = formatRial(150000); // ۱۵۰٬۰۰۰ ریال = ۱۵٬۰۰۰ تومان
|
||
expect(out).toContain('تومان');
|
||
expect(out).toMatch(PERSIAN_DIGITS);
|
||
expect(out).toContain(new Intl.NumberFormat('fa-IR').format(15000));
|
||
});
|
||
it('صفر را هم فرمت میکند', () => {
|
||
expect(formatRial(0)).toContain('تومان');
|
||
});
|
||
});
|
||
|
||
describe('rialToToman / tomanToRial', () => {
|
||
it('ریال↔تومان با ضریب ۱۰', () => {
|
||
expect(rialToToman(150000)).toBe(15000);
|
||
expect(tomanToRial(15000)).toBe(150000);
|
||
expect(tomanToRial(rialToToman(500))).toBe(500);
|
||
});
|
||
});
|
||
|
||
describe('formatNumber', () => {
|
||
it('رقم فارسی برمیگرداند', () => {
|
||
expect(formatNumber(1234)).toMatch(PERSIAN_DIGITS);
|
||
});
|
||
});
|
||
|
||
describe('toDate', () => {
|
||
it('null و رشته خالی → null', () => {
|
||
expect(toDate(null)).toBeNull();
|
||
expect(toDate(undefined)).toBeNull();
|
||
expect(toDate('')).toBeNull();
|
||
});
|
||
it('عدد را بهعنوان ثانیه (×۱۰۰۰) تفسیر میکند', () => {
|
||
const d = toDate(1_700_000_000);
|
||
expect(d).toBeInstanceOf(Date);
|
||
expect(d!.getTime()).toBe(1_700_000_000 * 1000);
|
||
});
|
||
it('Y-m-d را ظهر محلی میگیرد (نه نیمهشب UTC)', () => {
|
||
const d = toDate('2024-03-21');
|
||
expect(d!.getHours()).toBe(12);
|
||
expect(d!.getFullYear()).toBe(2024);
|
||
});
|
||
it('رشته ISO کامل را پارس میکند', () => {
|
||
const d = toDate('2024-03-21T08:30:00Z');
|
||
expect(d).toBeInstanceOf(Date);
|
||
expect(isNaN(d!.getTime())).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('formatDate', () => {
|
||
it('ورودی نامعتبر → "—"', () => {
|
||
expect(formatDate(null)).toBe('—');
|
||
expect(formatDate('not-a-date')).toBe('—');
|
||
});
|
||
it('timestamp ثانیهای معتبر → رشته شمسی با رقم فارسی', () => {
|
||
const out = formatDate(1_700_000_000);
|
||
expect(out).not.toBe('—');
|
||
expect(out).toMatch(PERSIAN_DIGITS);
|
||
});
|
||
});
|
||
|
||
describe('formatDateTime', () => {
|
||
it('ورودی نامعتبر → "—"', () => {
|
||
expect(formatDateTime(undefined)).toBe('—');
|
||
});
|
||
it('معتبر → شامل ساعت و رقم فارسی', () => {
|
||
const out = formatDateTime(1_700_000_000);
|
||
expect(out).not.toBe('—');
|
||
expect(out).toMatch(PERSIAN_DIGITS);
|
||
});
|
||
});
|
||
|
||
describe('toGregorianDate', () => {
|
||
it('YYYY-MM-DD با صفر پیشرو', () => {
|
||
expect(toGregorianDate(new Date(2024, 0, 5))).toBe('2024-01-05');
|
||
expect(toGregorianDate(new Date(2024, 11, 31))).toBe('2024-12-31');
|
||
});
|
||
});
|
||
|
||
describe('maskMobile', () => {
|
||
it('شماره ۱۱ رقمی را ماسک میکند', () => {
|
||
expect(maskMobile('09123456789')).toBe('0912***789');
|
||
});
|
||
it('شماره کوتاهتر از ۷ رقم بدون تغییر', () => {
|
||
expect(maskMobile('123')).toBe('123');
|
||
});
|
||
});
|
||
|
||
describe('cn', () => {
|
||
it('مقادیر falsy را حذف و بقیه را با فاصله میچسباند', () => {
|
||
expect(cn('a', false, null, undefined, 'b')).toBe('a b');
|
||
expect(cn()).toBe('');
|
||
});
|
||
});
|
||
|
||
describe('toEnglishDigits', () => {
|
||
it('رقم فارسی به انگلیسی', () => {
|
||
expect(toEnglishDigits('۰۹۱۲۳۴۵۶۷۸۹')).toBe('09123456789');
|
||
});
|
||
it('رقم عربی به انگلیسی', () => {
|
||
expect(toEnglishDigits('٠٩١٢')).toBe('0912');
|
||
});
|
||
it('ورودی خالی → رشته خالی', () => {
|
||
expect(toEnglishDigits('')).toBe('');
|
||
});
|
||
it('کاراکترهای غیررقمی را دستنخورده نگه میدارد', () => {
|
||
expect(toEnglishDigits('شماره ۰۹')).toBe('شماره 09');
|
||
});
|
||
});
|
||
|
||
describe('digitsOnly', () => {
|
||
it('رقم فارسی و عربی مخلوط را نرمال و غیررقم را حذف میکند', () => {
|
||
expect(digitsOnly('۱۲٣٤-56 ب')).toBe('123456');
|
||
});
|
||
it('با maxLen برش میزند', () => {
|
||
expect(digitsOnly('۱۲۳۴۵۶۷۸۹۰۱۲', 10)).toBe('1234567890');
|
||
});
|
||
it('ورودی خالی → رشته خالی', () => {
|
||
expect(digitsOnly('')).toBe('');
|
||
});
|
||
it('فقط حروف → خالی', () => {
|
||
expect(digitsOnly('کد ملی')).toBe('');
|
||
});
|
||
});
|
||
|
||
describe('formatYear', () => {
|
||
it('سال شمسی را بدون جداکنندهی هزارگان میدهد', () => {
|
||
expect(formatYear(1404)).toBe('۱۴۰۴');
|
||
});
|
||
it('برخلاف formatNumber جداکننده نمیگذارد', () => {
|
||
expect(formatNumber(1404)).not.toBe(formatYear(1404));
|
||
});
|
||
});
|
||
|
||
describe('parseUserNumber', () => {
|
||
it('رقم فارسی را به عدد تبدیل میکند', () => {
|
||
expect(parseUserNumber('۲۵')).toBe(25);
|
||
});
|
||
it('رقم عربی را به عدد تبدیل میکند', () => {
|
||
expect(parseUserNumber('٢٥')).toBe(25);
|
||
});
|
||
it('جداکننده هزارگان و فاصله را نادیده میگیرد', () => {
|
||
expect(parseUserNumber('1,200')).toBe(1200);
|
||
expect(parseUserNumber('۸۵٬۰۰۰')).toBe(85000);
|
||
expect(parseUserNumber(' ۹۰ ')).toBe(90);
|
||
});
|
||
it('اعشار فارسی را میپذیرد', () => {
|
||
expect(parseUserNumber('۱۲.۵')).toBe(12.5);
|
||
});
|
||
it('عدد منفی را میپذیرد', () => {
|
||
expect(parseUserNumber('-۳')).toBe(-3);
|
||
});
|
||
it('عدد را دستنخورده رد میکند', () => {
|
||
expect(parseUserNumber(42)).toBe(42);
|
||
});
|
||
it('خالی و null → null', () => {
|
||
expect(parseUserNumber('')).toBeNull();
|
||
expect(parseUserNumber(null)).toBeNull();
|
||
expect(parseUserNumber(undefined)).toBeNull();
|
||
});
|
||
it('ورودی نامعتبر → null و هرگز NaN', () => {
|
||
expect(parseUserNumber('abc')).toBeNull();
|
||
expect(parseUserNumber('۱۲ب۳')).toBeNull();
|
||
expect(parseUserNumber('.')).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('parseUserNumberClamped', () => {
|
||
it('مقدار داخل بازه را دستنخورده برمیگرداند', () => {
|
||
expect(parseUserNumberClamped('۲۵', 0, 100)).toBe(25);
|
||
});
|
||
it('بیشتر از سقف را به سقف میبرد', () => {
|
||
expect(parseUserNumberClamped('۱۵۰', 0, 100)).toBe(100);
|
||
});
|
||
it('کمتر از کف را به کف میبرد', () => {
|
||
expect(parseUserNumberClamped('-۵', 0, 100)).toBe(0);
|
||
});
|
||
it('ورودی نامعتبر → null (نه کف)', () => {
|
||
expect(parseUserNumberClamped('abc', 0, 100)).toBeNull();
|
||
expect(parseUserNumberClamped('', 0, 100)).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('persianSafeNumber', () => {
|
||
it('رشتهی فارسی را قبل از عدد شدن نرمال میکند', () => {
|
||
const schema = persianSafeNumber(z.coerce.number());
|
||
expect(schema.parse('۱۲۳')).toBe(123);
|
||
});
|
||
it('عدد را دستنخورده رد میکند', () => {
|
||
const schema = persianSafeNumber(z.coerce.number());
|
||
expect(schema.parse(42)).toBe(42);
|
||
});
|
||
});
|
||
|
||
describe('iranNationalCodeSchema', () => {
|
||
it('کد ملی فارسی را میپذیرد و لاتین برمیگرداند', () => {
|
||
expect(iranNationalCodeSchema.parse('۰۰۱۲۳۴۵۶۷۸')).toBe('0012345678');
|
||
});
|
||
it('کمتر از ۱۰ رقم رد میشود', () => {
|
||
expect(() => iranNationalCodeSchema.parse('12345')).toThrow();
|
||
});
|
||
it('نسخهی اختیاری خالی را میپذیرد', () => {
|
||
expect(iranNationalCodeOptionalSchema.parse('')).toBe('');
|
||
expect(() => iranNationalCodeOptionalSchema.parse('123')).toThrow();
|
||
});
|
||
});
|
||
|
||
describe('sanitizeMobileInput', () => {
|
||
it('غیررقم حذف، رقم فارسی نرمال، حداکثر ۱۱ رقم', () => {
|
||
expect(sanitizeMobileInput('۰۹۱۲-۳۴۵ ۶۷۸۹۰۱۲')).toBe('09123456789');
|
||
});
|
||
it('فقط حروف → خالی', () => {
|
||
expect(sanitizeMobileInput('abc')).toBe('');
|
||
});
|
||
});
|
||
|
||
describe('isValidIranMobile / IRAN_MOBILE_RE', () => {
|
||
it.each([
|
||
['09123456789', true],
|
||
['۰۹۱۲۳۴۵۶۷۸۹', true],
|
||
['9123456789', false],
|
||
['08123456789', false],
|
||
['0912345678', false],
|
||
['091234567890', false],
|
||
])('%s → %s', (input, expected) => {
|
||
expect(isValidIranMobile(input as string)).toBe(expected);
|
||
});
|
||
it('regex مستقیم روی ارقام انگلیسی', () => {
|
||
expect(IRAN_MOBILE_RE.test('09120000000')).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('iranMobileSchema', () => {
|
||
it('رقم فارسی را پذیرفته و نرمال میکند', () => {
|
||
expect(iranMobileSchema.parse('۰۹۱۲۳۴۵۶۷۸۹')).toBe('09123456789');
|
||
});
|
||
it('جداکنندهها را حذف میکند', () => {
|
||
expect(iranMobileSchema.parse('0912-345-6789')).toBe('09123456789');
|
||
});
|
||
it('شماره نامعتبر → ناموفق', () => {
|
||
const r = iranMobileSchema.safeParse('12345');
|
||
expect(r.success).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('iranMobileOptionalSchema', () => {
|
||
it('رشته خالی مجاز است', () => {
|
||
expect(iranMobileOptionalSchema.parse('')).toBe('');
|
||
});
|
||
it('شماره معتبر نرمال میشود', () => {
|
||
expect(iranMobileOptionalSchema.parse('۰۹۱۲۳۴۵۶۷۸۹')).toBe('09123456789');
|
||
});
|
||
it('شماره نیمهکاره → ناموفق', () => {
|
||
expect(iranMobileOptionalSchema.safeParse('0912').success).toBe(false);
|
||
});
|
||
});
|