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:
hamed
2026-06-28 22:58:15 +03:30
parent 439bb0868e
commit 6084dc5d6b
15 changed files with 2083 additions and 2146 deletions
+105
View File
@@ -0,0 +1,105 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { useAuthStore } from '@/stores/authStore';
const initial = useAuthStore.getInitialState();
function jsonRes(body: unknown, ok = true): Response {
return { ok, json: () => Promise.resolve(body) } as unknown as Response;
}
beforeEach(() => {
localStorage.clear();
useAuthStore.setState(initial, true);
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(jsonRes({ success: false })));
});
describe('login', () => {
it('توکن را ست و isAuthenticated را true می‌کند', () => {
useAuthStore.getState().login('tok', 'refresh');
const s = useAuthStore.getState();
expect(s.token).toBe('tok');
expect(s.refreshToken).toBe('refresh');
expect(s.isAuthenticated).toBe(true);
});
it('fetchMe (userinfo) را صدا می‌زند', () => {
useAuthStore.getState().login('tok');
expect(fetch).toHaveBeenCalledWith('/oauth/userinfo', expect.any(Object));
});
});
describe('logout', () => {
it('state را پاک می‌کند و وقتی refreshToken هست به /oauth/logout POST می‌زند', () => {
useAuthStore.setState({ token: 'tok', refreshToken: 'rt', isAuthenticated: true });
useAuthStore.getState().logout();
const s = useAuthStore.getState();
expect(s.token).toBeNull();
expect(s.isAuthenticated).toBe(false);
expect(fetch).toHaveBeenCalledWith('/oauth/logout', expect.objectContaining({ method: 'POST' }));
});
});
describe('refresh', () => {
it('بدون refreshToken → null و بدون fetch', async () => {
const out = await useAuthStore.getState().refresh();
expect(out).toBeNull();
expect(fetch).not.toHaveBeenCalled();
});
it('پاسخ موفق → توکن جدید برمی‌گرداند و state را به‌روز می‌کند', async () => {
useAuthStore.setState({ refreshToken: 'rt' });
(fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
jsonRes({ access_token: 'new-tok', refresh_token: 'new-rt' }),
);
const out = await useAuthStore.getState().refresh();
expect(out).toBe('new-tok');
expect(useAuthStore.getState().token).toBe('new-tok');
expect(useAuthStore.getState().refreshToken).toBe('new-rt');
});
it('پاسخ بدون access_token → null', async () => {
useAuthStore.setState({ refreshToken: 'rt' });
(fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(jsonRes({}, false));
expect(await useAuthStore.getState().refresh()).toBeNull();
});
});
describe('fetchMe', () => {
it('userinfo موفق → فیلدهای کاربر را ست می‌کند', async () => {
(fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
jsonRes({
success: true,
data: {
uuid: 'u1',
realName: 'دکتر تست',
primary_role: 'doctor',
available_contexts: [{ type: 'doctor', db_uuid: 'd1', name: 'مطب', role: 'doctor' }],
},
}),
);
await useAuthStore.getState().fetchMe();
const s = useAuthStore.getState();
expect(s.userUuid).toBe('u1');
expect(s.userName).toBe('دکتر تست');
expect(s.primaryRole).toBe('doctor');
expect(s.availableContexts).toHaveLength(1);
});
});
describe('switchContext', () => {
it('پاسخ موفق → db و context و primaryRole را ست می‌کند', async () => {
(fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
jsonRes({
success: true,
data: {
db_uuid: 'db2',
db_key: 'key2',
context: { type: 'clinic', db_uuid: 'db2', name: 'کلینیک', role: 'clinic', doctor_uuid: 'doc9' },
},
}),
);
await useAuthStore.getState().switchContext('db2');
const s = useAuthStore.getState();
expect(s.dbUuid).toBe('db2');
expect(s.dbKey).toBe('key2');
expect(s.primaryRole).toBe('clinic');
expect(s.doctorUuid).toBe('doc9');
});
});
+66
View File
@@ -0,0 +1,66 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useUiStore, HUES } from '@/stores/uiStore';
const initial = useUiStore.getInitialState();
beforeEach(() => {
localStorage.clear();
useUiStore.setState(initial, true);
document.documentElement.className = '';
document.documentElement.removeAttribute('data-theme');
document.documentElement.removeAttribute('data-density');
});
describe('sidebar', () => {
it('toggleSidebar وضعیت را برعکس می‌کند', () => {
expect(useUiStore.getState().sidebarOpen).toBe(true);
useUiStore.getState().toggleSidebar();
expect(useUiStore.getState().sidebarOpen).toBe(false);
});
it('setSidebarOpen مقدار صریح می‌گذارد', () => {
useUiStore.getState().setSidebarOpen(false);
expect(useUiStore.getState().sidebarOpen).toBe(false);
});
});
describe('darkMode + applyTheme', () => {
it('toggleDarkMode کلاس dark و data-theme را روی root می‌گذارد', () => {
useUiStore.getState().toggleDarkMode();
expect(useUiStore.getState().darkMode).toBe(true);
expect(document.documentElement.classList.contains('dark')).toBe(true);
expect(document.documentElement.getAttribute('data-theme')).toBe('dark');
});
it('toggle دوباره به light برمی‌گرداند', () => {
useUiStore.getState().toggleDarkMode();
useUiStore.getState().toggleDarkMode();
expect(useUiStore.getState().darkMode).toBe(false);
expect(document.documentElement.classList.contains('dark')).toBe(false);
expect(document.documentElement.getAttribute('data-theme')).toBe('light');
});
});
describe('density', () => {
it('setDensity مقدار و data-density را تنظیم می‌کند', () => {
useUiStore.getState().setDensity('compact');
expect(useUiStore.getState().density).toBe('compact');
expect(document.documentElement.getAttribute('data-density')).toBe('compact');
});
});
describe('brandHue', () => {
it('setBrandHue متغیرهای CSS را روی root می‌نویسد', () => {
const hue = HUES[2];
useUiStore.getState().setBrandHue(hue);
expect(useUiStore.getState().brandHue).toEqual(hue);
expect(document.documentElement.style.getPropertyValue('--brand-h')).toBe(String(hue.h));
expect(document.documentElement.style.getPropertyValue('--brand-c')).toBe(String(hue.c));
});
});
describe('settingsPanel', () => {
it('toggleSettingsPanel وضعیت را برعکس می‌کند', () => {
expect(useUiStore.getState().settingsPanelOpen).toBe(false);
useUiStore.getState().toggleSettingsPanel();
expect(useUiStore.getState().settingsPanelOpen).toBe(true);
});
});