Files
clinicpro/assets/admin/pages/BlogReviewPage.test.tsx

116 lines
5.0 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { renderWithProviders } from '@/test/utils';
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
vi.mock('@/lib/api', () => ({
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
ApiError: class extends Error {},
}));
import { api } from '@/lib/api';
import BlogReviewPage from '@/pages/BlogReviewPage';
const get = api.get as ReturnType<typeof vi.fn>;
/** ردیف صف — همان شکل `toListArray()`: بدون body و faq و sources. */
const ROW = {
uuid: 'b-1',
title: 'لیزر موهای زائد چطور کار می‌کند؟',
slug: 'laser',
summary: 'خلاصهٔ کوتاه',
image_url: null,
tags: ['پوست'],
status: 'draft',
review_status: 'pending_review',
topic_slug: 'skin/laser',
created_at: 1_700_000_000,
};
/** پاسخ اندپوینت جزئیات — double-nested، همان قرارداد فرم ویرایش مقاله. */
const DETAIL = {
success: true,
data: {
data: {
...ROW,
body: '<p>متن کامل مقاله اینجاست.</p>',
reading_time: 7,
primary_keyword: 'لیزر موهای زائد',
faq: [{ q: 'چند جلسه لازم است؟', a: 'معمولاً شش تا هشت جلسه.' }],
sources: [{ url: 'https://example.org/a', title: 'راهنمای پوست' }],
},
},
};
function mockApi(detail: unknown = DETAIL) {
get.mockImplementation((url: string) => {
if (url.includes('/review-queue')) {
return Promise.resolve({
success: true,
data: [ROW],
meta: { totalRecords: 1, totalPages: 1, currentPage: 1 },
errors: [],
});
}
return Promise.resolve(detail);
});
}
describe('BlogReviewPage — مودال بازبینی', () => {
beforeEach(() => { get.mockReset(); mockApi(); });
/**
* صف با `toListArray()` ساخته می‌شود و `body` ندارد؛ مودال باید متن کامل را از
* اندپوینت جزئیات بگیرد، وگرنه بازبین یک قاب خالی می‌بیند.
*/
it('با کلیک روی بازبینی، متن کامل مقاله را می‌گیرد و نشان می‌دهد', async () => {
renderWithProviders(<BlogReviewPage />, { route: '/admin/blog-review' });
fireEvent.click(await screen.findByRole('button', { name: 'بازبینی' }));
await waitFor(() => expect(screen.getByText('متن کامل مقاله اینجاست.')).toBeInTheDocument());
expect(get).toHaveBeenCalledWith('/api/v1/admin/blog/b-1');
});
it('پرسش‌های متداول و منابع و برچسب‌ها را هم نشان می‌دهد', async () => {
renderWithProviders(<BlogReviewPage />, { route: '/admin/blog-review' });
fireEvent.click(await screen.findByRole('button', { name: 'بازبینی' }));
await waitFor(() => expect(screen.getByText('پرسش‌های متداول')).toBeInTheDocument());
expect(screen.getByText('چند جلسه لازم است؟')).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'راهنمای پوست' })).toHaveAttribute('href', 'https://example.org/a');
expect(screen.getByText('پوست')).toBeInTheDocument();
});
/** بازبین باید بداند چه چیزی را تأیید می‌کند: زمان مطالعه و کلیدواژه و موضوع. */
it('شناسنامهٔ مقاله را بالای متن می‌آورد', async () => {
renderWithProviders(<BlogReviewPage />, { route: '/admin/blog-review' });
fireEvent.click(await screen.findByRole('button', { name: 'بازبینی' }));
await waitFor(() => expect(screen.getByText('۷ دقیقه مطالعه')).toBeInTheDocument());
expect(screen.getByText('کلیدواژه: لیزر موهای زائد')).toBeInTheDocument();
});
/** تا نیامدنِ متن، دکمه‌های تصمیم نباید روی یک قاب خالی بنشینند. */
it('پیش از رسیدن متن، حالت بارگذاری نشان می‌دهد', async () => {
get.mockImplementation((url: string) => {
if (url.includes('/review-queue')) {
return Promise.resolve({
success: true,
data: [ROW],
meta: { totalRecords: 1, totalPages: 1, currentPage: 1 },
errors: [],
});
}
return new Promise(() => {}); // هرگز resolve نمی‌شود
});
renderWithProviders(<BlogReviewPage />, { route: '/admin/blog-review' });
fireEvent.click(await screen.findByRole('button', { name: 'بازبینی' }));
expect(await screen.findByText('در حال بارگذاری مقاله...')).toBeInTheDocument();
});
});