test(web): increase frontend test coverage to ~70% page coverage

- Fix vitest config to include [locale] directory tests (was excluded)
- Fix register.spec.tsx: use getByRole('heading') to avoid duplicate text match
- Fix search.spec.tsx: add QueryClientProvider wrapper and mock saved searches hook
- Add 12 new page test files covering dashboard, admin, public, and OAuth pages:
  - dashboard (main, profile, payments, subscription, KYC)
  - admin (dashboard, users)
  - public (landing, pricing)
  - analytics
  - OAuth callbacks (Google, Zalo)
- 29 test files, 174 tests, 16/23 pages covered (69.6%)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-10 23:14:16 +07:00
parent d62eb5f164
commit 68b65cb848
15 changed files with 1122 additions and 7 deletions

View File

@@ -0,0 +1,79 @@
/* eslint-disable import-x/order */
import { render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('lucide-react', () => ({
Users: () => <span data-testid="icon-users" />,
Home: () => <span data-testid="icon-home" />,
ClipboardCheck: () => <span data-testid="icon-clipboard" />,
Clock: () => <span data-testid="icon-clock" />,
UserCheck: () => <span data-testid="icon-usercheck" />,
ShieldCheck: () => <span data-testid="icon-shieldcheck" />,
ArrowUpRight: () => <span data-testid="icon-arrowup" />,
ArrowDownRight: () => <span data-testid="icon-arrowdown" />,
TrendingUp: () => <span data-testid="icon-trending" />,
RefreshCw: () => <span data-testid="icon-refresh" />,
}));
vi.mock('@/lib/admin-api', () => ({
adminApi: {
getDashboardStats: vi.fn().mockResolvedValue({
totalUsers: 5000,
totalListings: 1200,
activeListings: 800,
pendingModerationCount: 15,
totalAgents: 300,
verifiedAgents: 150,
totalTransactions: 4500,
newUsersLast30Days: 200,
newListingsLast30Days: 50,
}),
getRevenueStats: vi.fn().mockResolvedValue([
{ period: '2025-11', totalRevenue: 50000000, subscriptionRevenue: 30000000, listingFeeRevenue: 20000000, transactionCount: 100 },
{ period: '2025-12', totalRevenue: 65000000, subscriptionRevenue: 40000000, listingFeeRevenue: 25000000, transactionCount: 130 },
]),
},
}));
import AdminDashboardPage from '../page';
describe('AdminDashboardPage', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders admin dashboard heading', async () => {
render(<AdminDashboardPage />);
await waitFor(() => {
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
});
it('renders stat cards with data', async () => {
render(<AdminDashboardPage />);
await waitFor(() => {
expect(screen.getByText('Tổng người dùng')).toBeInTheDocument();
expect(screen.getByText('Tổng tin đăng')).toBeInTheDocument();
expect(screen.getByText('Tin đang hoạt động')).toBeInTheDocument();
expect(screen.getByText('Chờ kiểm duyệt')).toBeInTheDocument();
});
});
it('renders revenue chart section', async () => {
render(<AdminDashboardPage />);
await waitFor(() => {
expect(screen.getByText('Doanh thu 6 tháng gần nhất')).toBeInTheDocument();
});
});
it('renders refresh button', async () => {
render(<AdminDashboardPage />);
await waitFor(() => {
expect(screen.getByRole('button', { name: /làm mới/i })).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,97 @@
/* eslint-disable import-x/order */
import { render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('lucide-react', () => ({
Search: () => <span data-testid="icon-search" />,
RefreshCw: () => <span data-testid="icon-refresh" />,
ChevronLeft: () => <span data-testid="icon-left" />,
ChevronRight: () => <span data-testid="icon-right" />,
UserX: () => <span data-testid="icon-userx" />,
UserCheck: () => <span data-testid="icon-usercheck" />,
Eye: () => <span data-testid="icon-eye" />,
X: () => <span data-testid="icon-x" />,
}));
vi.mock('@/lib/admin-api', () => ({
adminApi: {
getUsers: vi.fn().mockResolvedValue({
data: [
{
id: 'u1',
fullName: 'Nguyen Van A',
phone: '0912345678',
email: 'a@test.com',
role: 'BUYER',
kycStatus: 'NONE',
isActive: true,
createdAt: '2024-01-01T00:00:00.000Z',
},
{
id: 'u2',
fullName: 'Tran Thi B',
phone: '0987654321',
email: null,
role: 'AGENT',
kycStatus: 'VERIFIED',
isActive: true,
createdAt: '2024-02-01T00:00:00.000Z',
},
],
total: 2,
page: 1,
totalPages: 1,
}),
getUserDetail: vi.fn(),
banUser: vi.fn(),
},
}));
import AdminUsersPage from '../users/page';
describe('AdminUsersPage', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders user management heading', async () => {
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('Quản lý người dùng')).toBeInTheDocument();
});
});
it('renders user list', async () => {
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('Nguyen Van A')).toBeInTheDocument();
expect(screen.getByText('Tran Thi B')).toBeInTheDocument();
});
});
it('renders search input', async () => {
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByPlaceholderText(/tìm theo tên/i)).toBeInTheDocument();
});
});
it('renders role filter', async () => {
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText('Tất cả vai trò')).toBeInTheDocument();
});
});
it('renders empty detail panel placeholder', async () => {
render(<AdminUsersPage />);
await waitFor(() => {
expect(screen.getByText(/chọn người dùng để xem chi tiết/i)).toBeInTheDocument();
});
});
});