feat: add comprehensive tests for UI components, hooks, and API interactions
- Implement tests for Pagination, StatusBadge, ConfirmDialog, and MobileInput components. - Add tests for useSubscription, usePaymentConfig, and usePwaInstall hooks. - Create tests for API requests in the api module, including success and error handling. - Add utility function tests for formatting and validating Iranian mobile numbers. - Implement tests for BlogFormPage and BlogsPage to validate form submissions and data fetching. - Add tests for LoginPage to ensure proper validation and state management. - Create tests for authStore and uiStore to validate state management and functionality. - Set up Vitest configuration and testing utilities for consistent testing environment.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const { refreshMock, logoutMock } = vi.hoisted(() => ({
|
||||
refreshMock: vi.fn(),
|
||||
logoutMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/authStore', () => ({
|
||||
useAuthStore: { getState: () => ({ refresh: refreshMock, logout: logoutMock }) },
|
||||
}));
|
||||
|
||||
import { api, ApiError } from '@/lib/api';
|
||||
|
||||
const replaceMock = vi.fn();
|
||||
|
||||
function jsonRes(body: unknown, ok = true, status = 200): Response {
|
||||
return { ok, status, json: () => Promise.resolve(body) } as unknown as Response;
|
||||
}
|
||||
|
||||
function setToken(token: string) {
|
||||
localStorage.setItem('clinicpro-auth', JSON.stringify({ state: { token } }));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
refreshMock.mockReset();
|
||||
logoutMock.mockReset();
|
||||
replaceMock.mockReset();
|
||||
vi.stubGlobal('fetch', vi.fn());
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { replace: replaceMock },
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
describe('request — موفق', () => {
|
||||
it('بدنه JSON را برمیگرداند', async () => {
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(
|
||||
jsonRes({ success: true, data: { id: 1 } }),
|
||||
);
|
||||
const out = await api.get<{ data: { id: number } }>('/api/v1/x');
|
||||
expect(out).toEqual({ success: true, data: { id: 1 } });
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('وقتی توکن هست هدر Authorization میفرستد', async () => {
|
||||
setToken('tok-123');
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({ ok: 1 }));
|
||||
await api.get('/api/v1/x');
|
||||
const headers = (fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].headers;
|
||||
expect(headers['Authorization']).toBe('Bearer tok-123');
|
||||
});
|
||||
|
||||
it('بدون توکن، هدر Authorization نمیفرستد', async () => {
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({ ok: 1 }));
|
||||
await api.get('/api/v1/x');
|
||||
const headers = (fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].headers;
|
||||
expect(headers['Authorization']).toBeUndefined();
|
||||
expect(headers['Content-Type']).toBe('application/json');
|
||||
});
|
||||
|
||||
it('post بدنه را JSON.stringify و method=POST میفرستد', async () => {
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({ ok: 1 }));
|
||||
await api.post('/api/v1/x', { a: 1 });
|
||||
const opts = (fetch as ReturnType<typeof vi.fn>).mock.calls[0][1];
|
||||
expect(opts.method).toBe('POST');
|
||||
expect(opts.body).toBe(JSON.stringify({ a: 1 }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('request — خطاهای غیر۴۰۱', () => {
|
||||
it('۴۲۲ → ApiError با code/message از errors[0]', async () => {
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(
|
||||
jsonRes({ errors: [{ code: 'ERR_VALIDATION', message: 'نامعتبر' }] }, false, 422),
|
||||
);
|
||||
await expect(api.get('/api/v1/x')).rejects.toMatchObject({
|
||||
status: 422,
|
||||
code: 'ERR_VALIDATION',
|
||||
message: 'نامعتبر',
|
||||
});
|
||||
});
|
||||
|
||||
it('بدنه بدون errors → ERR_UNKNOWN', async () => {
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({}, false, 500));
|
||||
const err = (await api.get('/api/v1/x').catch((e) => e)) as ApiError;
|
||||
expect(err).toBeInstanceOf(ApiError);
|
||||
expect(err.code).toBe('ERR_UNKNOWN');
|
||||
expect(err.status).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('request — جریان ۴۰۱', () => {
|
||||
it('refresh موفق → یکبار retry و موفقیت', async () => {
|
||||
refreshMock.mockResolvedValue('new-token');
|
||||
(fetch as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce(jsonRes({}, false, 401))
|
||||
.mockResolvedValueOnce(jsonRes({ success: true, data: 'ok' }));
|
||||
|
||||
const out = await api.get('/api/v1/x');
|
||||
expect(out).toEqual({ success: true, data: 'ok' });
|
||||
expect(refreshMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('refresh ناموفق → logout + redirect + ApiError(401)', async () => {
|
||||
refreshMock.mockResolvedValue(null);
|
||||
(fetch as ReturnType<typeof vi.fn>).mockResolvedValue(jsonRes({}, false, 401));
|
||||
|
||||
const err = (await api.get('/api/v1/x').catch((e) => e)) as ApiError;
|
||||
expect(err).toBeInstanceOf(ApiError);
|
||||
expect(err.status).toBe(401);
|
||||
expect(err.code).toBe('ERR_UNAUTHORIZED');
|
||||
expect(logoutMock).toHaveBeenCalledTimes(1);
|
||||
expect(replaceMock).toHaveBeenCalledWith('/admin/login');
|
||||
});
|
||||
|
||||
it('de-dup: دو درخواست همزمان ۴۰۱ فقط یکبار refresh میزنند', async () => {
|
||||
refreshMock.mockImplementation(
|
||||
() => new Promise((r) => setTimeout(() => r('new-token'), 10)),
|
||||
);
|
||||
const f = fetch as ReturnType<typeof vi.fn>;
|
||||
f.mockImplementation(() => {
|
||||
const n = f.mock.calls.length;
|
||||
return Promise.resolve(n <= 2 ? jsonRes({}, false, 401) : jsonRes({ ok: 1 }));
|
||||
});
|
||||
|
||||
await Promise.all([api.get('/api/v1/a'), api.get('/api/v1/b')]);
|
||||
expect(refreshMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetch).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user